Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/fmt/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,9 @@ impl<'a, 'src> Ctx<'a, 'src> {
NodeKind::Block { name, params, sep, body } => {
self.block_node(name, params, sep, &body, at)
}
NodeKind::Type { .. } | NodeKind::Enum { .. } | NodeKind::Union { .. } => {
todo!("type-decl layout")
}
}
}

Expand Down
20 changes: 20 additions & 0 deletions src/fmt/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,26 @@ impl<'a, 'src> Printer<'a, 'src> {
self.node(inner);
}

// --- type declarations ---
NodeKind::Type { params, sep, body } => {
self.keyword(node_loc, "type");
self.node(params);
self.tok(&sep);
self.exprs(&body);
}
NodeKind::Enum { params, sep, body } => {
self.keyword(node_loc, "enum");
self.node(params);
self.tok(&sep);
self.exprs(&body);
}
NodeKind::Union { params, sep, body } => {
self.keyword(node_loc, "union");
self.node(params);
self.tok(&sep);
self.exprs(&body);
}

// --- custom blocks ---
NodeKind::Block { name, params, sep, body } => {
self.node(name);
Expand Down
25 changes: 25 additions & 0 deletions src/passes/ast/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,9 @@ fn fmt_node(ast: &Ast<'_>, id: AstId, out: &mut MappedWriter, depth: usize) {
out.push('\'');
}
}
NodeKind::Type { params, sep, body } => fmt_type_decl(ast, "type", params, &sep, &body.items, out, depth),
NodeKind::Enum { params, sep, body } => fmt_type_decl(ast, "enum", params, &sep, &body.items, out, depth),
NodeKind::Union { params, sep, body } => fmt_type_decl(ast, "union", params, &sep, &body.items, out, depth),
NodeKind::Block { name, params, sep, body } => {
fmt_node(ast, name, out, depth);
out.push(' ');
Expand Down Expand Up @@ -572,6 +575,28 @@ fn fmt_fn(ast: &Ast<'_>, params: AstId, sep: &Token, body: &[AstId], out: &mut M
fmt_fn_with_inline(ast, params, sep, body, out, depth, true);
}

// Format a `type` / `enum` / `union` declaration: keyword, optional generic
// params, then `:` body (or the unit `_` form).
fn fmt_type_decl(ast: &Ast<'_>, kw: &str, params: AstId, sep: &Token, body: &[AstId], out: &mut MappedWriter, depth: usize) {
out.push_str(kw);
// Unit form `type _`: keyword then the `_` sep, no body.
if sep.src == "_" {
out.push(' ');
out.mark(sep.loc);
out.push('_');
return;
}
// Generic params (Patterns), only when non-empty.
let has_params = matches!(&ast.nodes.get(params).kind, NodeKind::Patterns(e) if !e.items.is_empty());
if has_params {
out.push(' ');
fmt_node(ast, params, out, depth);
}
out.mark(sep.loc);
out.push(':');
fmt_body(ast, body, out, depth, true);
}

fn fmt_fn_with_inline(ast: &Ast<'_>, params: AstId, sep: &Token, body: &[AstId], out: &mut MappedWriter, depth: usize, allow_apply_inline: bool) {
let inline = body.len() == 1 && if allow_apply_inline {
is_inline_expr(ast, body[0])
Expand Down
45 changes: 45 additions & 0 deletions src/passes/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,23 @@ pub enum NodeKind<'src> {
// Try — unwrap Ok or propagate Err from enclosing function
Try(AstId),

// --- type declarations ---

// Type — product type (record/tuple). params (Patterns, generic params; empty
// if none) + sep (':' for a block, '_' for the unit form `type _`) + body
// (fields: Arm ':' for named/record, bare exprs for positional/tuple; empty
// for unit).
Type { params: AstId, sep: Token<'src>, body: Exprs<'src> },

// Enum — closed sum that mints constructors. params (Patterns generic params)
// + sep (':') + body (constructor members: bare Ident for nullary, Apply for
// payload-carrying).
Enum { params: AstId, sep: Token<'src>, body: Exprs<'src> },

// Union — open union of existing types. params (Patterns generic params) +
// sep (':') + body (member type expressions).
Union { params: AstId, sep: Token<'src>, body: Exprs<'src> },

// --- custom blocks ---

// Block — name (Ident) + params (Patterns) + sep (:) + body
Expand Down Expand Up @@ -479,6 +496,12 @@ pub fn walk<'src, 'a>(
walk(ast, *lhs, f);
for &stmt_id in body.items.iter() { walk(ast, stmt_id, f); }
}
NodeKind::Type { params, body, .. }
| NodeKind::Enum { params, body, .. }
| NodeKind::Union { params, body, .. } => {
walk(ast, *params, f);
for &stmt_id in body.items.iter() { walk(ast, stmt_id, f); }
}
NodeKind::Block { name, params, body, .. } => {
walk(ast, *name, f);
walk(ast, *params, f);
Expand Down Expand Up @@ -687,6 +710,28 @@ fn print_node(ast: &Ast, id: AstId, out: &mut crate::sourcemap::MappedWriter, de
print_node(ast, stmt_id, out, depth + 1);
}
}
NodeKind::Type { params, sep, body }
| NodeKind::Enum { params, sep, body }
| NodeKind::Union { params, sep, body } => {
let label = match node.kind {
NodeKind::Type { .. } => "Type",
NodeKind::Enum { .. } => "Enum",
_ => "Union",
};
out.push_str(label);
out.push_str(" '"); out.push_str(sep.src); out.push('\'');
// Unit form `type _` has empty params + body and prints as just `Type '_'`.
if sep.src == "_" && body.items.is_empty() {
return;
}
out.push(',');
out.push('\n');
print_node(ast, *params, out, depth + 1);
for &stmt_id in body.items.iter() {
out.push('\n');
print_node(ast, stmt_id, out, depth + 1);
}
}
NodeKind::Block { name, params, sep, body } => {
out.push_str("Block '"); out.push_str(sep.src); out.push_str("',");
out.push('\n');
Expand Down
94 changes: 94 additions & 0 deletions src/passes/ast/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ impl<'src> Parser<'src> {
let mut block_names = HashMap::new();
block_names.insert("fn", BlockMode::Ast);
block_names.insert("match", BlockMode::Ast);
block_names.insert("type", BlockMode::Ast);
block_names.insert("enum", BlockMode::Ast);
block_names.insert("union", BlockMode::Ast);
let mut p = Parser {
lexer,
src,
Expand Down Expand Up @@ -417,6 +420,7 @@ impl<'src> Parser<'src> {

if name == "fn" { return self.parse_fn(loc); }
if name == "match" { return self.parse_match_expr(loc); }
if Self::is_type_keyword(name) { return self.parse_type_decl(loc, name); }
if self.block_names.contains_key(name) { return self.parse_block(loc, name); }

// Tagged template string: ident immediately adjacent to StrStart → raw template
Expand Down Expand Up @@ -473,6 +477,7 @@ impl<'src> Parser<'src> {
if let NodeKind::Ident(name) = self.get(head).kind {
if name == "fn" { return self.parse_fn(self.loc_of(head)); }
if name == "match" { return self.parse_match_expr(self.loc_of(head)); }
if Self::is_type_keyword(name) { return self.parse_type_decl(self.loc_of(head), name); }
if self.block_names.contains_key(name) && name != "fn" && name != "match" {
return self.parse_block(self.loc_of(head), name);
}
Expand Down Expand Up @@ -793,6 +798,10 @@ impl<'src> Parser<'src> {
self.bump();
return self.parse_match_expr(name_tok.loc);
}
if Self::is_type_keyword(name_tok.src) {
self.bump();
return self.parse_type_decl(name_tok.loc, name_tok.src);
}
if self.block_names.contains_key(name_tok.src) {
self.bump();
return self.parse_block(name_tok.loc, name_tok.src);
Expand Down Expand Up @@ -1568,6 +1577,90 @@ impl<'src> Parser<'src> {

// --- fn ---

// type / enum / union — reserved keywords introducing a type declaration.
fn is_type_keyword(s: &str) -> bool {
matches!(s, "type" | "enum" | "union")
}

// Parse a `type` / `enum` / `union` declaration. The keyword is already
// consumed; `kw` is its source ("type"/"enum"/"union").
//
// type _ -> unit: Type with sep '_', empty params + body
// type T: <block> -> generic params before the colon
// type: a, b -> inline positional (tuple) body
// type: <block> -> block body (record arms / tuple exprs / members)
fn parse_type_decl(&mut self, kw_loc: Loc, kw: &'src str) -> ParseResult {
// Unit form `type _`: a bare wildcard with no following colon.
if self.at(TokenKind::Ident) && self.peek().src == "_" {
let underscore = *self.peek();
// Only the unit form when `_` is NOT a generic param (`type _:` would be
// a colon-block with a `_` param — not currently meaningful, treat the
// bare `type _` as unit).
if self.peek_next().kind != TokenKind::Colon {
self.bump(); // consume `_`
let params = self.node(NodeKind::Patterns(Exprs::empty()), underscore.loc);
let loc = Loc { start: kw_loc.start, end: underscore.loc.end };
let body = Exprs::empty();
return Ok(self.make_type_node(kw, params, underscore, body, loc));
}
}

// Generic params: everything between the keyword and the `:`.
let (params, _) = self.parse_params()?;

// Body after the colon: block (arms/exprs/spreads) or inline comma-separated exprs.
let sep = self.expect(TokenKind::Colon)?;
let body = if self.at(TokenKind::BlockStart) {
self.bump();
self.parse_block_items(|p| p.parse_type_body_item())?
} else {
// Inline body: comma-separated expressions (positional tuple form).
self.parse_inline_type_items()?
};

let end = body.items.last().map(|&id| self.loc_of(id).end).unwrap_or(self.loc_of(params).end);
let loc = Loc { start: kw_loc.start, end };
Ok(self.make_type_node(kw, params, sep, body, loc))
}

fn make_type_node(
&mut self,
kw: &'src str,
params: AstId,
sep: Token<'src>,
body: Exprs<'src>,
loc: Loc,
) -> AstId {
let kind = match kw {
"type" => NodeKind::Type { params, sep, body },
"enum" => NodeKind::Enum { params, sep, body },
_ => NodeKind::Union { params, sep, body },
};
self.node(kind, loc)
}

// A single line of a `type`/`enum`/`union` block body: a `..Foo` spread
// (type-level extension), a `key: T` arm (record field), or a bare type
// expression (tuple positional / enum constructor / union member).
fn parse_type_body_item(&mut self) -> ParseResult {
if Self::is_spread_op(self.peek()) {
return self.parse_spread();
}
self.parse_expr_or_arm()
}

// Inline (non-block) type body: comma-separated expressions, e.g. `type: u8, i8`.
fn parse_inline_type_items(&mut self) -> Result<Exprs<'src>, ParseError> {
let mut items: Vec<AstId> = vec![];
let mut seps: Vec<Token<'src>> = vec![];
items.push(self.parse_expr()?);
while self.at(TokenKind::Comma) {
seps.push(self.bump());
items.push(self.parse_expr()?);
}
Ok(Exprs { items: items.into_boxed_slice(), seps })
}

fn parse_fn(&mut self, fn_loc: Loc) -> ParseResult {
// "fn" already consumed
let is_fn_match = self.at(TokenKind::Ident) && self.peek().src == "match";
Expand Down Expand Up @@ -2100,4 +2193,5 @@ mod tests {
test_macros::include_fink_tests!("src/passes/ast/test_module.fnk");
test_macros::include_fink_tests!("src/passes/ast/test_member_apply.fnk");
test_macros::include_fink_tests!("src/passes/ast/test_errors.fnk");
test_macros::include_fink_tests!("src/passes/ast/test_types.fnk");
}
Loading
Loading