feat: improved parsing and regression test suite

This commit is contained in:
Brett Broadhurst 2026-02-27 18:07:02 -07:00
parent 4ebdd3c66e
commit 619eb3b338
Failed to generate hash of commit
39 changed files with 1116 additions and 339 deletions

6
.gitignore vendored
View file

@ -4,8 +4,10 @@
# Directories # Directories
.cache/ .cache/
.zig-cache .zig-cache/
zig-out zig-out/
build/
build-*
# Temporary Files / Logs # Temporary Files / Logs
*.log *.log

5
CMakeLists.txt Normal file
View file

@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.20)
project(InkCompiler LANGUAGES NONE)
add_subdirectory(testing/regression)

View file

@ -7,7 +7,6 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("src/root.zig"), .root_source_file = b.path("src/root.zig"),
.target = target, .target = target,
}); });
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "inkc", .name = "inkc",
.root_module = b.createModule(.{ .root_module = b.createModule(.{

View file

@ -12,10 +12,14 @@ errors: []Error,
pub const Node = struct { pub const Node = struct {
tag: Tag, tag: Tag,
source_start: usize, loc: Span,
source_end: usize,
data: Data, data: Data,
pub const Span = struct {
start: usize,
end: usize,
};
pub const Tag = enum { pub const Tag = enum {
file, file,
false_literal, false_literal,
@ -107,6 +111,98 @@ pub const Node = struct {
children: ?[]*Node, children: ?[]*Node,
}, },
}; };
fn create(gpa: std.mem.Allocator, tag: Tag, loc: Span) !*Node {
const node = try gpa.create(Node);
node.* = .{ .tag = tag, .loc = loc, .data = undefined };
return node;
}
pub fn createLeaf(gpa: std.mem.Allocator, tag: Tag, loc: Span) !*Node {
const node = try Node.create(gpa, tag, loc);
node.data = .{ .leaf = undefined };
return node;
}
pub fn createBinary(
gpa: std.mem.Allocator,
tag: Tag,
span: Span,
lhs: ?*Node,
rhs: ?*Node,
) !*Node {
const node = try Node.create(gpa, tag, span);
node.data = .{
.bin = .{ .lhs = lhs, .rhs = rhs },
};
return node;
}
pub fn createList(
gpa: std.mem.Allocator,
tag: Tag,
span: Span,
items: ?[]*Node,
) !*Node {
const node = try Node.create(gpa, tag, span);
node.data = .{
.list = .{ .items = items },
};
return node;
}
pub fn createChoice(
gpa: std.mem.Allocator,
tag: Tag,
span: Span,
start_expr: ?*Node,
option_expr: ?*Node,
inner_expr: ?*Node,
) !*Node {
const node = try Node.create(gpa, tag, span);
node.data = .{
.choice_expr = .{
.start_expr = start_expr,
.option_expr = option_expr,
.inner_expr = inner_expr,
},
};
return node;
}
pub fn createSwitch(
gpa: std.mem.Allocator,
tag: Tag,
loc: Span,
condition_expr: ?*Node,
cases_list: ?[]*Node,
) !*Node {
const node = try Node.create(gpa, tag, loc);
node.data = .{
.switch_stmt = .{
.condition_expr = condition_expr,
.cases = cases_list,
},
};
return node;
}
pub fn createKnot(
gpa: std.mem.Allocator,
tag: Tag,
loc: Span,
prototype: *Node,
children: ?[]*Node,
) !*Node {
const node = try Node.create(gpa, tag, loc);
node.data = .{
.knot_decl = .{
.prototype = prototype,
.children = children,
},
};
return node;
}
}; };
pub const Error = struct { pub const Error = struct {
@ -134,121 +230,6 @@ pub const Error = struct {
}; };
}; };
fn create(
gpa: std.mem.Allocator,
tag: Node.Tag,
source_start: usize,
source_end: usize,
) !*Node {
const node = try gpa.create(Node);
node.* = .{
.tag = tag,
.source_start = source_start,
.source_end = source_end,
.data = undefined,
};
return node;
}
pub fn createLeafNode(
gpa: std.mem.Allocator,
tag: Node.Tag,
source_start: usize,
source_end: usize,
) !*Node {
const node = try Ast.create(gpa, tag, source_start, source_end);
node.*.data = .{ .leaf = undefined };
return node;
}
pub fn createBinaryNode(
gpa: std.mem.Allocator,
tag: Node.Tag,
source_start: usize,
source_end: usize,
lhs: ?*Node,
rhs: ?*Node,
) !*Node {
const node = try Ast.create(gpa, tag, source_start, source_end);
node.*.data = .{
.bin = .{
.lhs = lhs,
.rhs = rhs,
},
};
return node;
}
pub fn createListNode(
gpa: std.mem.Allocator,
tag: Node.Tag,
source_start: usize,
source_end: usize,
items: ?[]*Node,
) !*Node {
const node = try Ast.create(gpa, tag, source_start, source_end);
node.*.data = .{
.list = .{ .items = items },
};
return node;
}
pub fn createChoiceExprNode(
gpa: std.mem.Allocator,
tag: Node.Tag,
source_start: usize,
source_end: usize,
start_expr: ?*Node,
option_expr: ?*Node,
inner_expr: ?*Node,
) !*Node {
const node = try Ast.create(gpa, tag, source_start, source_end);
node.*.data = .{
.choice_expr = .{
.start_expr = start_expr,
.option_expr = option_expr,
.inner_expr = inner_expr,
},
};
return node;
}
pub fn createSwitchNode(
gpa: std.mem.Allocator,
tag: Node.Tag,
source_start: usize,
source_end: usize,
condition_expr: ?*Node,
cases_list: ?[]*Node,
) !*Node {
const node = try Ast.create(gpa, tag, source_start, source_end);
node.*.data = .{
.switch_stmt = .{
.condition_expr = condition_expr,
.cases = cases_list,
},
};
return node;
}
pub fn createKnotDeclNode(
gpa: std.mem.Allocator,
tag: Node.Tag,
source_start: usize,
source_end: usize,
prototype: *Node,
children: ?[]*Node,
) !*Node {
const node = try Ast.create(gpa, tag, source_start, source_end);
node.*.data = .{
.knot_decl = .{
.prototype = prototype,
.children = children,
},
};
return node;
}
pub fn parse( pub fn parse(
gpa: std.mem.Allocator, gpa: std.mem.Allocator,
arena: std.mem.Allocator, arena: std.mem.Allocator,

View file

@ -208,15 +208,15 @@ fn writeType(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
fn writeLexeme(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void { fn writeLexeme(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
const bytes = r.tree.source; const bytes = r.tree.source;
const lexeme = bytes[node.source_start..node.source_end]; const lexeme = bytes[node.loc.start..node.loc.end];
try r.tty_config.setColor(writer, .yellow); try r.tty_config.setColor(writer, .yellow);
try writer.print("`{s}`", .{lexeme}); try writer.print("`{s}`", .{lexeme});
try r.tty_config.setColor(writer, .reset); try r.tty_config.setColor(writer, .reset);
} }
fn writeLineSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void { fn writeLineSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
const line_start = r.lines.calculateLine(node.source_start); const line_start = r.lines.calculateLine(node.loc.start);
const line_end = r.lines.calculateLine(node.source_end); const line_end = r.lines.calculateLine(node.loc.end);
try r.tty_config.setColor(writer, .white); try r.tty_config.setColor(writer, .white);
try writer.writeByte('<'); try writer.writeByte('<');
@ -242,10 +242,10 @@ fn writeLineSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !voi
} }
fn writeColumnSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void { fn writeColumnSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
const line_start = r.lines.calculateLine(node.source_start); const line_start = r.lines.calculateLine(node.loc.start);
const line_range = r.lines.ranges.items[line_start]; const line_range = r.lines.ranges.items[line_start];
const column_start = (node.source_start - line_range.start); const column_start = (node.loc.start - line_range.start);
const column_end = (node.source_end - line_range.start); const column_end = (node.loc.end - line_range.start);
try r.tty_config.setColor(writer, .white); try r.tty_config.setColor(writer, .white);
try writer.writeByte('<'); try writer.writeByte('<');
@ -271,10 +271,10 @@ fn writeColumnSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !v
} }
fn writeLineColumnSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void { fn writeLineColumnSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
const line_start = r.lines.calculateLine(node.source_start); const line_start = r.lines.calculateLine(node.loc.start);
const line_range = r.lines.ranges.items[line_start]; const line_range = r.lines.ranges.items[line_start];
const column_start = (node.source_start - line_range.start); const column_start = (node.loc.start - line_range.start);
const column_end = (node.source_end - line_range.start); const column_end = (node.loc.end - line_range.start);
try r.tty_config.setColor(writer, .white); try r.tty_config.setColor(writer, .white);
try writer.writeByte('<'); try writer.writeByte('<');

View file

@ -1,10 +1,9 @@
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert;
const tok = @import("tokenizer.zig"); const tok = @import("tokenizer.zig");
const Ast = @import("Ast.zig");
const assert = std.debug.assert;
const Token = tok.Token; const Token = tok.Token;
const Tokenizer = tok.Tokenizer; const Tokenizer = tok.Tokenizer;
const Ast = @import("Ast.zig");
const Parse = @This(); const Parse = @This();
gpa: std.mem.Allocator, gpa: std.mem.Allocator,
@ -99,7 +98,7 @@ fn getTokenInfixType(tag: Token.Tag) Ast.Node.Tag {
.star => .multiply_expr, .star => .multiply_expr,
.slash => .divide_expr, .slash => .divide_expr,
// .question_mark => .contains_expr, // .question_mark => .contains_expr,
.equal => .assign_stmt, // .equal => .assign_stmt,
.ampersand_ampersand, .keyword_and => .logical_and_expr, .ampersand_ampersand, .keyword_and => .logical_and_expr,
.pipe_pipe, .keyword_or => .logical_or_expr, .pipe_pipe, .keyword_or => .logical_or_expr,
.equal_equal => .logical_equality_expr, .equal_equal => .logical_equality_expr,
@ -165,7 +164,6 @@ fn checkTokenInSet(p: *const Parse, tag_set: []const Token.Tag) bool {
fn nextToken(p: *Parse) Token { fn nextToken(p: *Parse) Token {
assert(p.grammar_stack.items.len > 0); assert(p.grammar_stack.items.len > 0);
const token = p.token; const token = p.token;
const context = p.grammar_stack.getLast(); const context = p.grammar_stack.getLast();
p.token = p.tokenizer.next(context.grammar); p.token = p.tokenizer.next(context.grammar);
@ -252,6 +250,7 @@ fn nodeListFromScratch(p: *Parse, start_offset: usize, end_offset: usize) Error!
const span = end_offset - start_offset; const span = end_offset - start_offset;
assert(span > 0); assert(span > 0);
const list = try p.arena.alloc(*Ast.Node, span); const list = try p.arena.alloc(*Ast.Node, span);
defer p.scratch.shrinkRetainingCapacity(start_offset); defer p.scratch.shrinkRetainingCapacity(start_offset);
@ -261,7 +260,6 @@ fn nodeListFromScratch(p: *Parse, start_offset: usize, end_offset: usize) Error!
list[li] = p.scratch.items[i]; list[li] = p.scratch.items[i];
li += 1; li += 1;
} }
return list; return list;
} }
@ -269,15 +267,14 @@ fn makeNodeSequence(
p: *Parse, p: *Parse,
context: *const StmtContext, context: *const StmtContext,
tag: Ast.Node.Tag, tag: Ast.Node.Tag,
bytes_start: usize, loc: Ast.Node.Span,
bytes_end: usize,
scratch_offset: usize, scratch_offset: usize,
) Error!*Ast.Node { ) Error!*Ast.Node {
var list: ?[]*Ast.Node = null; var list: ?[]*Ast.Node = null;
if (!p.isScratchEmpty(context)) { if (!p.isScratchEmpty(context)) {
list = try p.nodeListFromScratch(scratch_offset, p.scratch.items.len); list = try p.nodeListFromScratch(scratch_offset, p.scratch.items.len);
} }
return Ast.createListNode(p.arena, tag, bytes_start, bytes_end, list); return .createList(p.arena, tag, loc, list);
} }
fn isBlockStackEmpty(p: *Parse, context: *const StmtContext) bool { fn isBlockStackEmpty(p: *Parse, context: *const StmtContext) bool {
@ -337,23 +334,20 @@ fn collectBlock(p: *Parse, context: *StmtContext, level: usize) Error!?*Ast.Node
const block = p.peekBlockStack(context); const block = p.peekBlockStack(context);
if (block.level < level) return null; if (block.level < level) return null;
const bytes_start = block.source_offset; const span_start = block.source_offset;
var bytes_end: usize = 0; var span_end: usize = 0;
if (!p.isScratchEmpty(context)) { if (!p.isScratchEmpty(context)) {
const last = p.peekScratch(context); const last = p.peekScratch(context);
bytes_end = last.source_end; span_end = last.loc.end;
} else { } else {
bytes_end = bytes_start; span_end = span_start;
} }
var node = try p.makeNodeSequence( var node = try p.makeNodeSequence(context, .block_stmt, .{
context, .start = span_start,
.block_stmt, .end = span_end,
bytes_start, }, block.scratch_offset);
bytes_end,
block.scratch_offset,
);
node = try p.fixupBlock(context, node); node = try p.fixupBlock(context, node);
_ = p.popBlockStack(context); _ = p.popBlockStack(context);
return node; return node;
@ -407,13 +401,10 @@ fn collectContext(
} }
} }
const node = try p.makeNodeSequence( const node = try p.makeNodeSequence(context, .choice_stmt, .{
context, .start = choice_state.source_offset,
.choice_stmt, .end = p.token.loc.start,
choice_state.source_offset, }, choice_state.scratch_offset);
p.token.loc.start,
choice_state.scratch_offset,
);
try p.scratch.append(p.gpa, node); try p.scratch.append(p.gpa, node);
} }
if (!should_gather) return p.collectBlock(context, level); if (!should_gather) return p.collectBlock(context, level);
@ -431,10 +422,11 @@ fn collectStitch(p: *Parse, context: *StmtContext) Error!?*Ast.Node {
.function_prototype => .function_decl, .function_prototype => .function_decl,
else => return node, else => return node,
}; };
const span_start = proto.source_start;
const span_end = if (node) |n| n.source_end else proto.source_end;
_ = p.popScratch(context); _ = p.popScratch(context);
return Ast.createBinaryNode(p.arena, tag, span_start, span_end, proto, node); return .createBinary(p.arena, tag, .{
.start = proto.loc.start,
.end = if (node) |n| n.loc.end else proto.loc.end,
}, proto, node);
} }
fn collectKnot(p: *Parse, context: *StmtContext) Error!?*Ast.Node { fn collectKnot(p: *Parse, context: *StmtContext) Error!?*Ast.Node {
@ -449,10 +441,10 @@ fn collectKnot(p: *Parse, context: *StmtContext) Error!?*Ast.Node {
const list = try p.nodeListFromScratch(p.knot_offset + 1, p.scratch.items.len); const list = try p.nodeListFromScratch(p.knot_offset + 1, p.scratch.items.len);
defer _ = p.popScratch(context); defer _ = p.popScratch(context);
const bytes_start = proto.source_start; return .createKnot(p.arena, .knot_decl, .{
const bytes_end = if (child) |n| n.source_end else proto.source_end; .start = proto.loc.start,
.end = if (child) |n| n.loc.end else proto.loc.end,
return Ast.createKnotDeclNode(p.arena, .knot_decl, bytes_start, bytes_end, proto, list); }, proto, list);
} }
fn handleChoiceBranch(p: *Parse, context: *StmtContext, node: *Ast.Node) !void { fn handleChoiceBranch(p: *Parse, context: *StmtContext, node: *Ast.Node) !void {
@ -462,14 +454,14 @@ fn handleChoiceBranch(p: *Parse, context: *StmtContext, node: *Ast.Node) !void {
try p.block_stack.append(p.gpa, .{ try p.block_stack.append(p.gpa, .{
.level = 0, .level = 0,
.scratch_offset = p.scratch.items.len, .scratch_offset = p.scratch.items.len,
.source_offset = node.source_start, .source_offset = node.loc.start,
}); });
} }
if (p.isChoiceStackEmpty(context)) { if (p.isChoiceStackEmpty(context)) {
try p.choice_stack.append(p.gpa, .{ try p.choice_stack.append(p.gpa, .{
.level = level, .level = level,
.scratch_offset = p.scratch.items.len, .scratch_offset = p.scratch.items.len,
.source_offset = node.source_start, .source_offset = node.loc.start,
}); });
} else { } else {
const choice_state = p.peekChoiceStack(context); const choice_state = p.peekChoiceStack(context);
@ -483,11 +475,10 @@ fn handleChoiceBranch(p: *Parse, context: *StmtContext, node: *Ast.Node) !void {
.source_offset = p.token.loc.start, .source_offset = p.token.loc.start,
}); });
} }
try p.choice_stack.append(p.gpa, .{ try p.choice_stack.append(p.gpa, .{
.level = level, .level = level,
.scratch_offset = p.scratch.items.len, .scratch_offset = p.scratch.items.len,
.source_offset = node.source_start, .source_offset = node.loc.start,
}); });
} else if (level == choice_state.level) { } else if (level == choice_state.level) {
const t_node = try p.collectBlock(context, level); const t_node = try p.collectBlock(context, level);
@ -500,16 +491,15 @@ fn handleChoiceBranch(p: *Parse, context: *StmtContext, node: *Ast.Node) !void {
} }
fn handleGatherPoint(p: *Parse, context: *StmtContext, node: **Ast.Node) !void { fn handleGatherPoint(p: *Parse, context: *StmtContext, node: **Ast.Node) !void {
const token = p.token; const main_token = p.token;
const level = context.level; const level = context.level;
if (p.isBlockStackEmpty(context)) { if (p.isBlockStackEmpty(context)) {
assert(p.isChoiceStackEmpty(context)); assert(p.isChoiceStackEmpty(context));
try p.block_stack.append(p.gpa, .{ try p.block_stack.append(p.gpa, .{
.level = 0, .level = 0,
.scratch_offset = p.scratch.items.len, .scratch_offset = p.scratch.items.len,
.source_offset = node.*.source_start, .source_offset = node.*.loc.start,
}); });
} }
// Gather points terminate compound statements at the appropriate level. // Gather points terminate compound statements at the appropriate level.
@ -522,20 +512,16 @@ fn handleGatherPoint(p: *Parse, context: *StmtContext, node: **Ast.Node) !void {
try p.block_stack.append(p.gpa, .{ try p.block_stack.append(p.gpa, .{
.level = choice_state.level, .level = choice_state.level,
.scratch_offset = p.scratch.items.len, .scratch_offset = p.scratch.items.len,
.source_offset = node.*.source_start, .source_offset = node.*.loc.start,
}); });
} }
} else if (!p.isScratchEmpty(context)) { } else if (!p.isScratchEmpty(context)) {
const tmp = (try p.collectContext(context, level - 1, true)) orelse @panic("FUCK!"); const tmp = (try p.collectContext(context, level - 1, true)) orelse @panic("FUCK!");
if (tmp.tag == .choice_stmt) { if (tmp.tag == .choice_stmt) {
node.* = try Ast.createBinaryNode( node.* = try Ast.Node.createBinary(p.arena, .gathered_stmt, .{
p.arena, .start = tmp.loc.start,
.gathered_stmt, .end = main_token.loc.start,
tmp.source_start, }, tmp, node.*);
token.loc.start,
tmp,
node.*,
);
} }
if (!p.isBlockStackEmpty(context)) { if (!p.isBlockStackEmpty(context)) {
const b = p.peekBlockStack(context); const b = p.peekBlockStack(context);
@ -553,7 +539,7 @@ fn handleContentStmt(p: *Parse, context: *StmtContext, node: *Ast.Node) !void {
try p.block_stack.append(p.gpa, .{ try p.block_stack.append(p.gpa, .{
.level = 0, .level = 0,
.scratch_offset = p.scratch.items.len, .scratch_offset = p.scratch.items.len,
.source_offset = node.source_start, .source_offset = node.loc.start,
}); });
} }
if (!p.isChoiceStackEmpty(context)) { if (!p.isChoiceStackEmpty(context)) {
@ -564,7 +550,7 @@ fn handleContentStmt(p: *Parse, context: *StmtContext, node: *Ast.Node) !void {
try p.block_stack.append(p.gpa, .{ try p.block_stack.append(p.gpa, .{
.level = choice_state.level, .level = choice_state.level,
.scratch_offset = p.scratch.items.len, .scratch_offset = p.scratch.items.len,
.source_offset = node.source_start, .source_offset = node.loc.start,
}); });
} }
} }
@ -600,10 +586,11 @@ fn expectExpr(p: *Parse) Error!*Ast.Node {
} }
fn parseAtom(p: *Parse, tag: Ast.Node.Tag) Error!*Ast.Node { fn parseAtom(p: *Parse, tag: Ast.Node.Tag) Error!*Ast.Node {
const token = p.nextToken(); const main_token = p.nextToken();
const span_start = token.loc.start; return .createLeaf(p.arena, tag, .{
const span_end = token.loc.end; .start = main_token.loc.start,
return Ast.createLeafNode(p.arena, tag, span_start, span_end); .end = main_token.loc.end,
});
} }
fn parseIdentifier(p: *Parse) Error!*Ast.Node { fn parseIdentifier(p: *Parse) Error!*Ast.Node {
@ -640,14 +627,15 @@ fn parsePrimaryExpr(p: *Parse) Error!?*Ast.Node {
fn parsePrefixExpr(p: *Parse) Error!?*Ast.Node { fn parsePrefixExpr(p: *Parse) Error!?*Ast.Node {
switch (p.token.tag) { switch (p.token.tag) {
.keyword_not, .minus, .exclaimation_mark => { .keyword_not, .minus, .exclaimation_mark => {
const token = p.nextToken(); const main_token = p.nextToken();
const lhs = try parsePrefixExpr(p); const lhs = try parsePrefixExpr(p);
if (lhs == null) return null; if (lhs == null) return null;
const tag = getTokenPrefixType(token.tag); const tag = getTokenPrefixType(main_token.tag);
const bytes_start = token.loc.start; return .createBinary(p.arena, tag, .{
const bytes_end = if (lhs) |n| n.source_end else p.token.loc.end; .start = main_token.loc.start,
return Ast.createBinaryNode(p.arena, tag, bytes_start, bytes_end, lhs, null); .end = if (lhs) |n| n.loc.end else p.token.loc.end,
}, lhs, null);
}, },
else => return parsePrimaryExpr(p), else => return parsePrimaryExpr(p),
} }
@ -673,10 +661,10 @@ fn parseInfixExpr(
next_node = try parseInfixExpr(p, null, token_precedence); next_node = try parseInfixExpr(p, null, token_precedence);
if (next_node) |rhs| { if (next_node) |rhs| {
const tag = getTokenInfixType(token.tag); const tag = getTokenInfixType(token.tag);
const bytes_start = if (lhs) |n| n.source_start else token.loc.start; lhs = try Ast.Node.createBinary(p.arena, tag, .{
const bytes_end = rhs.source_end; .start = if (lhs) |n| n.loc.start else token.loc.start,
.end = rhs.loc.end,
lhs = try Ast.createBinaryNode(p.arena, tag, bytes_start, bytes_end, lhs, rhs); }, lhs, rhs);
} else return null; } else return null;
} else break; } else break;
} }
@ -689,7 +677,7 @@ fn parseExpression(p: *Parse) Error!?*Ast.Node {
fn parseStringExpr(p: *Parse) Error!*Ast.Node { fn parseStringExpr(p: *Parse) Error!*Ast.Node {
assert(p.token.tag == .double_quote); assert(p.token.tag == .double_quote);
const leading_token = p.nextToken(); const main_token = p.nextToken();
while (true) switch (p.token.tag) { while (true) switch (p.token.tag) {
.double_quote, .newline, .eof => break, .double_quote, .newline, .eof => break,
@ -697,34 +685,42 @@ fn parseStringExpr(p: *Parse) Error!*Ast.Node {
}; };
const last_token = try p.expectToken(.double_quote, true); const last_token = try p.expectToken(.double_quote, true);
const span_start = leading_token.loc.start; const expr = try Ast.Node.createLeaf(p.arena, .string_literal, .{
const span_end = p.token.loc.start; .start = main_token.loc.end,
const expr = try Ast.createLeafNode(p.arena, .string_literal, leading_token.loc.end, last_token.loc.start); .end = last_token.loc.start,
return Ast.createBinaryNode(p.arena, .string_expr, span_start, span_end, expr, null); });
return .createBinary(p.arena, .string_expr, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
}, expr, null);
} }
fn parseContentString(p: *Parse, token_set: []const Token.Tag) Error!?*Ast.Node { fn parseContentString(p: *Parse, token_set: []const Token.Tag) Error!?*Ast.Node {
const main_token = p.token; const main_token = p.token;
while (!p.checkTokenInSet(token_set)) _ = p.nextToken(); while (!p.checkTokenInSet(token_set)) _ = p.nextToken();
const span_start = main_token.loc.start; return .createLeaf(p.arena, if (main_token.loc.start == p.token.loc.start)
const span_end = p.token.loc.start; .empty_string
const tag: Ast.Node.Tag = if (span_start == span_end) .empty_string else .string_literal; else
return Ast.createLeafNode(p.arena, tag, span_start, span_end); .string_literal, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
});
} }
fn parseExprStmt(p: *Parse, lhs: ?*Ast.Node) Error!*Ast.Node { fn parseExprStmt(p: *Parse, lhs: ?*Ast.Node) Error!*Ast.Node {
const token = p.token; const main_token = p.token;
const node = try parseInfixExpr(p, lhs, .none); const node = try parseInfixExpr(p, lhs, .none);
_ = try p.expectNewline(); _ = try p.expectNewline();
const bytes_start = if (lhs) |n| n.source_start else token.loc.start; return .createBinary(p.arena, .expr_stmt, .{
const bytes_end = p.token.loc.start; .start = if (lhs) |n| n.loc.start else main_token.loc.start,
return Ast.createBinaryNode(p.arena, .expr_stmt, bytes_start, bytes_end, node, null); .end = p.token.loc.start,
}, node, null);
} }
fn parseAssignStmt(p: *Parse) Error!*Ast.Node { fn parseAssignStmt(p: *Parse) Error!*Ast.Node {
const token = p.token; const main_token = p.token;
const lhs = try parseIdentifierExpr(p); const lhs = try parseIdentifierExpr(p);
if (!p.checkToken(.equal)) return parseExprStmt(p, lhs); if (!p.checkToken(.equal)) return parseExprStmt(p, lhs);
@ -733,21 +729,24 @@ fn parseAssignStmt(p: *Parse) Error!*Ast.Node {
const rhs = try p.expectExpr(); const rhs = try p.expectExpr();
_ = try p.expectNewline(); _ = try p.expectNewline();
const bytes_start = token.loc.start; return .createBinary(p.arena, .assign_stmt, .{
const bytes_end = p.token.loc.start; .start = main_token.loc.start,
return Ast.createBinaryNode(p.arena, .assign_stmt, bytes_start, bytes_end, lhs, rhs); .end = p.token.loc.start,
}, lhs, rhs);
} }
fn parseTempDecl(p: *Parse) Error!*Ast.Node { fn parseTempDecl(p: *Parse) Error!*Ast.Node {
const token = p.nextToken(); const main_token = p.nextToken();
const lhs = try p.expectIdentifier(); const lhs = try p.expectIdentifier();
_ = try p.expectToken(.equal, true); _ = try p.expectToken(.equal, true);
const rhs = try p.expectExpr(); const rhs = try p.expectExpr();
_ = try p.expectNewline(); _ = try p.expectNewline();
const bytes_start = token.loc.start; return .createBinary(p.arena, .temp_decl, .{
const bytes_end = p.token.loc.start; .start = main_token.loc.start,
return Ast.createBinaryNode(p.arena, .temp_decl, bytes_start, bytes_end, lhs, rhs); .end = p.token.loc.start,
}, lhs, rhs);
} }
fn parseTildeStmt(p: *Parse) Error!*Ast.Node { fn parseTildeStmt(p: *Parse) Error!*Ast.Node {
@ -755,7 +754,7 @@ fn parseTildeStmt(p: *Parse) Error!*Ast.Node {
defer p.popGrammar(); defer p.popGrammar();
_ = p.nextToken(); _ = p.nextToken();
const node: *Ast.Node = switch (p.token.tag) { const node = switch (p.token.tag) {
.keyword_temp => try parseTempDecl(p), .keyword_temp => try parseTempDecl(p),
.keyword_return => try parseReturnStmt(p), .keyword_return => try parseReturnStmt(p),
.identifier => try parseAssignStmt(p), .identifier => try parseAssignStmt(p),
@ -766,17 +765,17 @@ fn parseTildeStmt(p: *Parse) Error!*Ast.Node {
fn parseReturnStmt(p: *Parse) Error!*Ast.Node { fn parseReturnStmt(p: *Parse) Error!*Ast.Node {
var node: ?*Ast.Node = null; var node: ?*Ast.Node = null;
const token = p.nextToken(); const main_token = p.nextToken();
if (!p.checkToken(.newline) and !p.checkToken(.eof)) { if (!p.checkToken(.newline) and !p.checkToken(.eof)) {
node = try parseInfixExpr(p, null, .none); node = try parseInfixExpr(p, null, .none);
} }
_ = try p.expectNewline(); _ = try p.expectNewline();
return .createBinary(p.arena, .return_stmt, .{
const bytes_start = token.loc.start; .start = main_token.loc.start,
const bytes_end = p.token.loc.start; .end = p.token.loc.start,
}, node, null);
return Ast.createBinaryNode(p.arena, .return_stmt, bytes_start, bytes_end, node, null);
} }
fn parseIdentifierExpr(p: *Parse) Error!*Ast.Node { fn parseIdentifierExpr(p: *Parse) Error!*Ast.Node {
@ -787,97 +786,44 @@ fn parseIdentifierExpr(p: *Parse) Error!*Ast.Node {
.dot => { .dot => {
_ = p.nextToken(); _ = p.nextToken();
const rhs = try p.expectIdentifier(); const rhs = try p.expectIdentifier();
const bytes_start = lhs.source_start; lhs = try Ast.Node.createBinary(p.arena, .selector_expr, .{
const bytes_end = p.token.loc.start; .start = lhs.loc.start,
.end = p.token.loc.start,
lhs = try Ast.createBinaryNode( }, lhs, rhs);
p.arena,
.selector_expr,
bytes_start,
bytes_end,
lhs,
rhs,
);
}, },
.left_paren => { .left_paren => {
const rhs = try parseArgumentList(p); const rhs = try parseArgumentList(p);
const bytes_start = lhs.source_start; return .createBinary(p.arena, .call_expr, .{
const bytes_end = p.token.loc.start; .start = lhs.loc.start,
.end = p.token.loc.start,
return Ast.createBinaryNode( }, lhs, rhs);
p.arena,
.call_expr,
bytes_start,
bytes_end,
lhs,
rhs,
);
}, },
else => return lhs, else => return lhs,
} }
} }
} }
fn parseContentExpr(p: *Parse, token_set: []const Token.Tag) Error!?*Ast.Node {
const main_token = p.token;
const context = makeStmtContext(p, .block, null);
while (true) { fn parseDivertExpr(p: *Parse) Error!*Ast.Node {
var node: ?*Ast.Node = null; const main_token = p.nextToken();
if (!p.checkTokenInSet(token_set)) { const node = try parseIdentifierExpr(p);
node = try parseContentString(p, token_set); return .createBinary(p.arena, .divert_expr, .{
} else switch (p.token.tag) { .start = main_token.loc.start,
.eof, .newline, .right_brace => break, .end = p.token.loc.start,
// .left_brace => node = try parseLbraceExpr(p), }, node, null);
// .right_arrow => node = try parseDivertStmt(p),
//.INK_TT_GLUE => node = ink_parse_glue(p),
else => {
return p.fail(.unexpected_token, p.token);
},
}
if (node) |n| try p.scratch.append(p.gpa, n);
}
const span_start = main_token.loc.start;
const span_end = p.token.loc.start;
return p.makeNodeSequence(&context, .content, span_start, span_end, context.scratch_top);
} }
fn parseArgumentList(p: *Parse) Error!?*Ast.Node { fn parseDivertStmt(p: *Parse) Error!*Ast.Node {
const context = p.makeStmtContext(.block, null); try p.pushGrammar(.expression);
const token = try p.expectToken(.left_paren, true); defer p.popGrammar();
if (!p.checkToken(.right_paren)) {
var cnt: usize = 0;
while (true) : (cnt += 1) {
if (cnt == p.max_argument_count) {
return p.fail(.too_many_arguments, p.token);
}
const node = try parseInfixExpr(p, null, .none);
if (node) |n| try p.scratch.append(p.gpa, n);
if (p.checkToken(.comma)) {
_ = p.nextToken();
} else break;
}
}
_ = try p.expectToken(.right_paren, false);
const bytes_start = token.loc.start;
const bytes_end = p.token.loc.start;
return p.makeNodeSequence(&context, .argument_list, bytes_start, bytes_end, context.scratch_top);
}
fn parseContentStmt(p: *Parse) Error!*Ast.Node {
const token_set = [_]Token.Tag{
.left_brace, .right_brace, .right_arrow,
.glue, .newline, .eof,
};
const main_token = p.token; const main_token = p.token;
const node = try parseContentExpr(p, &token_set); const node = try parseDivertExpr(p);
const end_token = try p.expectNewline(); _ = try p.expectNewline();
const span_start = main_token.loc.start;
const span_end = end_token.loc.start; return .createBinary(p.arena, .divert_stmt, .{
return Ast.createBinaryNode(p.arena, .content_stmt, span_start, span_end, node, null); .start = main_token.loc.start,
.end = p.token.loc.start,
}, node, null);
} }
fn parseChoiceExpr(p: *Parse) Error!?*Ast.Node { fn parseChoiceExpr(p: *Parse) Error!?*Ast.Node {
@ -886,7 +832,7 @@ fn parseChoiceExpr(p: *Parse) Error!?*Ast.Node {
.right_bracket, .right_arrow, .newline, .right_bracket, .right_arrow, .newline,
.eof, .eof,
}; };
const token = p.token; const main_token = p.token;
var lhs: ?*Ast.Node = null; var lhs: ?*Ast.Node = null;
var mhs: ?*Ast.Node = null; var mhs: ?*Ast.Node = null;
var rhs: ?*Ast.Node = null; var rhs: ?*Ast.Node = null;
@ -920,50 +866,286 @@ fn parseChoiceExpr(p: *Parse) Error!?*Ast.Node {
} }
} }
} }
return .createChoice(p.arena, .choice_expr, .{
const bytes_start = token.loc.start; .start = main_token.loc.start,
const bytes_end = p.token.loc.start; .end = p.token.loc.start,
return Ast.createChoiceExprNode(p.arena, .choice_expr, bytes_start, bytes_end, lhs, mhs, rhs); }, lhs, mhs, rhs);
} }
fn parseChoiceStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node { fn parseChoiceStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node {
const token = p.token; const main_token = p.token;
const level = p.eatTokenLooped(token.tag, true); const level = p.eatTokenLooped(main_token.tag, true);
const node = try parseChoiceExpr(p); const node = try parseChoiceExpr(p);
const bytes_start = token.loc.start; const span_end = if (node) |n| n.loc.end else p.token.loc.start;
const bytes_end = if (node) |n| n.source_end else p.token.loc.start;
context.level = level; context.level = level;
if (p.checkToken(.newline)) _ = p.nextToken(); if (p.checkToken(.newline)) _ = p.nextToken();
const tag = getBranchTag(token.tag);
return Ast.createBinaryNode(p.arena, tag, bytes_start, bytes_end, node, null); return .createBinary(p.arena, getBranchTag(main_token.tag), .{
.start = main_token.loc.start,
.end = span_end,
}, node, null);
} }
fn parseGatherPointStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node { fn parseGatherPointStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node {
const token = p.token; const main_token = p.token;
const level = p.eatTokenLooped(token.tag, true); const level = p.eatTokenLooped(main_token.tag, true);
const bytes_start = token.loc.start; const span_end = p.token.loc.start;
const bytes_end = p.token.loc.start;
context.level = level; context.level = level;
return Ast.createBinaryNode(p.arena, .gather_point_stmt, bytes_start, bytes_end, null, null); p.eatToken(.whitespace);
p.eatToken(.newline);
return .createBinary(p.arena, .gather_point_stmt, .{
.start = main_token.loc.start,
.end = span_end,
}, null, null);
}
fn parseConditional(p: *Parse, main_token: Token, expr: ?*Ast.Node) Error!?*Ast.Node {
var context = p.makeStmtContext(.conditional, expr);
p.eatToken(.whitespace);
while (!p.checkToken(.eof) and !p.checkToken(.right_brace)) {
const node = parseStmt(p, &context) catch |err| switch (err) {
error.ParseError => {
if (p.panic_mode) p.synchronize();
p.eatToken(.newline);
continue;
},
else => |e| return e,
};
try p.scratch.append(p.gpa, node);
}
const end_token = try p.expectToken(.right_brace, true);
const node = try p.collectContext(&context, 0, false);
if (node) |n| try p.scratch.append(p.gpa, n);
const list = try p.nodeListFromScratch(context.scratch_top, p.scratch.items.len);
return .createSwitch(p.arena, if (expr != null and !context.is_block_created)
.switch_stmt
else if (expr == null and !context.is_block_created)
.multi_if_stmt
else
.if_stmt, .{
.start = main_token.loc.start,
.end = end_token.loc.start,
}, expr, list);
}
fn parseInlineIf(p: *Parse, main_token: Token, lhs: ?*Ast.Node) Error!?*Ast.Node {
const token_set = [_]Token.Tag{
.left_brace, .right_brace, .right_arrow,
.glue, .newline, .eof,
};
const scratch_top = p.scratch.items.len;
p.eatToken(.whitespace);
const content_node = try parseContentExpr(p, &token_set);
const end_token = try p.expectToken(.right_brace, true);
if (content_node) |n| try p.scratch.append(p.gpa, n);
const list = try p.nodeListFromScratch(scratch_top, p.scratch.items.len);
return try .createSwitch(p.arena, .if_stmt, .{
.start = main_token.loc.start,
.end = end_token.loc.end,
}, lhs, list);
}
fn parseLbraceExpr(p: *Parse) Error!?*Ast.Node {
const lbrace_token = p.token;
const lhs = n: {
try p.pushGrammar(.expression);
defer p.popGrammar();
_ = p.nextToken();
const node = try parseExpression(p);
break :n node;
};
if (lhs == null) {
try p.pushGrammar(.content);
defer p.popGrammar();
_ = try p.expectToken(.newline, true);
return parseConditional(p, lbrace_token, null);
}
if (p.checkToken(.right_brace)) {
const rbrace_token = p.nextToken();
return .createBinary(p.arena, .inline_logic_expr, .{
.start = lbrace_token.loc.start,
.end = rbrace_token.loc.end,
}, lhs, null);
}
_ = try p.expectToken(.colon, true);
if (p.checkToken(.newline)) {
_ = p.nextToken();
return parseConditional(p, lbrace_token, lhs);
} else {
_ = p.nextToken();
return parseInlineIf(p, lbrace_token, lhs);
}
}
fn parseContentExpr(p: *Parse, token_set: []const Token.Tag) Error!?*Ast.Node {
const main_token = p.token;
const context = makeStmtContext(p, .block, null);
while (true) {
var node: ?*Ast.Node = null;
if (!p.checkTokenInSet(token_set)) {
node = try parseContentString(p, token_set);
} else switch (p.token.tag) {
.eof, .newline, .right_brace => break,
.left_brace => node = try parseLbraceExpr(p),
.right_arrow => node = try parseDivertStmt(p),
//.INK_TT_GLUE => node = ink_parse_glue(p),
else => {
return p.fail(.unexpected_token, p.token);
},
}
if (node) |n| try p.scratch.append(p.gpa, n);
}
return p.makeNodeSequence(&context, .content, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
}, context.scratch_top);
}
fn parseContentStmt(p: *Parse) Error!*Ast.Node {
const token_set = [_]Token.Tag{
.left_brace, .right_brace, .right_arrow,
.glue, .newline, .eof,
};
const main_token = p.token;
const node = try parseContentExpr(p, &token_set);
const end_token = try p.expectNewline();
return .createBinary(p.arena, .content_stmt, .{
.start = main_token.loc.start,
.end = end_token.loc.start,
}, node, null);
}
fn parseParameterDecl(p: *Parse) Error!*Ast.Node {
const tag: Ast.Node.Tag = if (p.checkToken(.keyword_ref)) blk: {
_ = p.nextToken();
break :blk .ref_parameter_decl;
} else .parameter_decl;
const node = try p.expectIdentifier();
node.tag = tag;
return node;
}
fn parseParameterList(p: *Parse) Error!?*Ast.Node {
const context = p.makeStmtContext(.block, null);
const main_token = try p.expectToken(.left_paren, true);
if (!p.checkToken(.right_paren)) {
var cnt: usize = 0;
while (true) : (cnt += 1) {
if (cnt == p.max_argument_count) {
return p.fail(.too_many_parameters, p.token);
}
const node = try parseParameterDecl(p);
try p.scratch.append(p.gpa, node);
if (p.checkToken(.comma)) {
_ = p.nextToken();
} else break;
}
}
_ = try p.expectToken(.right_paren, true);
return p.makeNodeSequence(&context, .parameter_list, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
}, context.scratch_top);
}
fn parseArgumentList(p: *Parse) Error!?*Ast.Node {
const context = p.makeStmtContext(.block, null);
const main_token = try p.expectToken(.left_paren, true);
if (!p.checkToken(.right_paren)) {
var cnt: usize = 0;
while (true) : (cnt += 1) {
if (cnt == p.max_argument_count) {
return p.fail(.too_many_arguments, p.token);
}
const node = try parseInfixExpr(p, null, .none);
if (node) |n| try p.scratch.append(p.gpa, n);
if (p.checkToken(.comma)) {
_ = p.nextToken();
} else break;
}
}
_ = try p.expectToken(.right_paren, false);
return p.makeNodeSequence(&context, .argument_list, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
}, context.scratch_top);
}
fn parseConditionalBranch(p: *Parse, tag: Ast.Node.Tag) Error!?*Ast.Node {
const main_token = p.token;
var node_tag = tag;
var node: ?*Ast.Node = null;
if (p.checkToken(.keyword_else)) {
_ = p.nextToken();
node_tag = .else_branch;
} else {
node = try parseExpression(p);
if (node == null) return null;
}
if (!p.checkToken(.colon)) return null;
_ = p.nextToken();
if (p.checkToken(.newline)) _ = p.nextToken();
return .createBinary(p.arena, node_tag, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
}, node, null);
}
fn parseConditionalStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node {
try p.pushGrammar(.expression);
defer p.popGrammar();
_ = p.nextToken();
const node = try parseConditionalBranch(p, if (context.expression_node) |_|
.switch_case
else
.if_branch);
if (node) |n| return n;
p.rewindGrammar();
try p.pushGrammar(.content);
defer p.popGrammar();
_ = p.nextToken();
return parseGatherPointStmt(p, context);
} }
fn parseVar(p: *Parse, tag: Ast.Node.Tag) Error!*Ast.Node { fn parseVar(p: *Parse, tag: Ast.Node.Tag) Error!*Ast.Node {
try p.pushGrammar(.expression); try p.pushGrammar(.expression);
defer p.popGrammar(); defer p.popGrammar();
const token = p.nextToken(); const main_token = p.nextToken();
const lhs = try p.expectIdentifier(); const lhs = try p.expectIdentifier();
_ = try p.expectToken(.equal, true); _ = try p.expectToken(.equal, true);
const rhs = try p.expectExpr(); const rhs = try p.expectExpr();
const last_token = try p.expectNewline(); const last_token = try p.expectNewline();
const bytes_start = token.loc.start; return .createBinary(p.arena, tag, .{
const bytes_end = last_token.loc.start; .start = main_token.loc.start,
return Ast.createBinaryNode(p.arena, tag, bytes_start, bytes_end, lhs, rhs); .end = last_token.loc.start,
}, lhs, rhs);
} }
fn parseVarDecl(p: *Parse) Error!*Ast.Node { fn parseVarDecl(p: *Parse) Error!*Ast.Node {
@ -976,6 +1158,43 @@ fn parseConstDecl(p: *Parse) Error!*Ast.Node {
return parseVar(p, .const_decl); return parseVar(p, .const_decl);
} }
fn parseKnotDecl(p: *Parse) Error!*Ast.Node {
var tag: Ast.Node.Tag = .stitch_prototype;
var lhs: ?*Ast.Node = null;
var rhs: ?*Ast.Node = null;
const main_token = p.nextToken();
try p.pushGrammar(.expression);
defer p.popGrammar();
p.eatToken(.whitespace);
if (p.checkToken(.equal)) {
tag = .knot_prototype;
while (p.checkToken(.equal)) {
_ = p.nextToken();
}
}
if (p.checkToken(.keyword_function)) {
_ = p.nextToken();
tag = .function_prototype;
}
lhs = try p.expectIdentifier();
if (p.checkToken(.left_paren)) {
rhs = try parseParameterList(p);
}
while (p.checkToken(.equal) or p.checkToken(.equal_equal)) {
_ = p.nextToken();
}
_ = try p.expectNewline();
return .createBinary(p.arena, tag, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
}, lhs, rhs);
}
fn parseStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node { fn parseStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node {
p.eatToken(.whitespace); p.eatToken(.whitespace);
@ -983,15 +1202,14 @@ fn parseStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node {
.star, .plus => try parseChoiceStmt(p, context), .star, .plus => try parseChoiceStmt(p, context),
.minus => switch (context.tag) { .minus => switch (context.tag) {
.block => try parseGatherPointStmt(p, context), .block => try parseGatherPointStmt(p, context),
else => unreachable, .conditional => try parseConditionalStmt(p, context),
//.conditional => try parseConditionalStmt(p, context),
}, },
.equal, .equal_equal => switch (context.tag) { .equal, .equal_equal => switch (context.tag) {
//.block => try parseKnotDecl(p), .block => try parseKnotDecl(p),
else => try parseContentStmt(p), else => try parseContentStmt(p),
}, },
.tilde => try parseTildeStmt(p), .tilde => try parseTildeStmt(p),
//.right_arrow => try parseDivertStmt(p), .right_arrow => try parseDivertStmt(p),
.right_brace => { .right_brace => {
const token = p.nextToken(); const token = p.nextToken();
return p.fail(.unexpected_token, token); return p.fail(.unexpected_token, token);
@ -1022,12 +1240,11 @@ pub fn parseFile(p: *Parse) Error!*Ast.Node {
try p.pushGrammar(.content); try p.pushGrammar(.content);
defer p.popGrammar(); defer p.popGrammar();
const token = p.nextToken(); const main_token = p.nextToken();
while (!p.checkToken(.eof)) { while (!p.checkToken(.eof)) {
const node = parseStmt(p, &context) catch |err| switch (err) { const node = parseStmt(p, &context) catch |err| switch (err) {
error.ParseError => { error.ParseError => {
if (p.panic_mode) p.synchronize(); if (p.panic_mode) p.synchronize();
p.eatToken(.newline); p.eatToken(.newline);
continue; continue;
}, },
@ -1039,7 +1256,8 @@ pub fn parseFile(p: *Parse) Error!*Ast.Node {
const node = try p.collectKnot(&context); const node = try p.collectKnot(&context);
if (node) |n| try p.scratch.append(p.gpa, n); if (node) |n| try p.scratch.append(p.gpa, n);
const span_start = token.loc.start; return p.makeNodeSequence(&context, .file, .{
const span_end = p.token.loc.end; .start = main_token.loc.start,
return p.makeNodeSequence(&context, .file, span_start, span_end, context.scratch_top); .end = p.token.loc.end,
}, context.scratch_top);
} }

View file

@ -31,11 +31,25 @@ fn mainArgs(
) !void { ) !void {
var source_path: ?[]const u8 = null; var source_path: ?[]const u8 = null;
var arg_index: usize = 1; var arg_index: usize = 1;
var compile_only: bool = false;
var dump_ast: bool = false;
var use_stdin: bool = false;
var use_color: bool = false;
while (arg_index < args_list.len) : (arg_index += 1) { while (arg_index < args_list.len) : (arg_index += 1) {
const arg = args_list[arg_index]; const arg = args_list[arg_index];
if (std.mem.startsWith(u8, arg, "-")) { if (std.mem.startsWith(u8, arg, "-")) {
// TODO: Parse CLI options. if (std.mem.eql(u8, arg, "--stdin")) {
use_stdin = true;
} else if (std.mem.eql(u8, arg, "--compile-only")) {
compile_only = true;
} else if (std.mem.eql(u8, arg, "--dump-ast")) {
dump_ast = true;
} else if (std.mem.eql(u8, arg, "--use-color")) {
use_color = true;
} else {
fatal("invalid parameter: '{s}'", .{arg});
}
} else if (source_path == null) { } else if (source_path == null) {
source_path = arg; source_path = arg;
} else { } else {
@ -62,7 +76,8 @@ fn mainArgs(
var stdout_writer = stdout.writer(&stdout_buffer); var stdout_writer = stdout.writer(&stdout_buffer);
var story = try ink.Story.loadFromString(gpa, source_bytes, .{ var story = try ink.Story.loadFromString(gpa, source_bytes, .{
.stream_writer = &stdout_writer.interface, .dump_writer = &stdout_writer.interface,
.dump_use_color = use_color,
}); });
defer story.deinit(); defer story.deinit();
} }

View file

@ -4,8 +4,8 @@ pub const Ast = @import("Ast.zig");
pub const Story = struct { pub const Story = struct {
pub const LoadOptions = struct { pub const LoadOptions = struct {
stream_writer: *std.Io.Writer, dump_writer: *std.Io.Writer,
use_color: bool = true, dump_use_color: bool = true,
}; };
pub fn loadFromString( pub fn loadFromString(
@ -20,12 +20,12 @@ pub const Story = struct {
var ast = try Ast.parse(gpa, arena, source_bytes, "<STDIN>", 0); var ast = try Ast.parse(gpa, arena, source_bytes, "<STDIN>", 0);
defer ast.deinit(gpa); defer ast.deinit(gpa);
try ast.render(gpa, options.stream_writer, .{ try ast.render(gpa, options.dump_writer, .{
.use_color = options.use_color, .use_color = options.dump_use_color,
}); });
if (ast.errors.len > 0) { if (ast.errors.len > 0) {
try ast.renderErrors(gpa, options.stream_writer, .{ try ast.renderErrors(gpa, options.dump_writer, .{
.use_color = options.use_color, .use_color = options.dump_use_color,
}); });
return error.Invalid; return error.Invalid;
} }

View file

@ -0,0 +1,9 @@
set(LIT_CFG_DIR ${CMAKE_CURRENT_SOURCE_DIR})
configure_file(lit.site.cfg.py.in ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py @ONLY)
find_program(LLVM_LIT_EXECUTABLE NAMES llvm-lit lit)
add_custom_target(check
COMMAND ${LLVM_LIT_EXECUTABLE} -v "${CMAKE_CURRENT_BINARY_DIR}"
)

View file

@ -0,0 +1,14 @@
#!/usr/bin/env python3
import os
import lit.formats
config.name = "Ink Compiler Regression Tests"
config.suffixes = [".ink"]
config.tools = ["FileCheck"]
config.test_format = lit.formats.ShTest(execute_external=True)
config.test_source_root = os.path.dirname(__file__)
config.test_exec_root = os.path.join(config.project_build_root, "testing", "regression")
exe_path = os.path.join(config.project_zig_root, "bin", "inkc")
config.substitutions.append(("%ink-compiler", exe_path))

View file

@ -0,0 +1,8 @@
import os
config.project_source_root = r"@CMAKE_SOURCE_DIR@"
config.project_build_root = r"@CMAKE_BINARY_DIR@"
config.project_zig_root = os.path.join(r"@CMAKE_SOURCE_DIR@", "zig-out")
lit_config.load_config(
config, os.path.join(config.project_source_root, "testing", "regression", "lit.cfg.py"))

View file

@ -0,0 +1,11 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:11, line:11>
// CHECK-NEXT: `--ChoiceStmt <line:11, line:11>
// CHECK-NEXT: `--ChoiceStarStmt <line:11, col:1:7>
// CHECK-NEXT: `--ChoiceContentExpr <col:3, col:7>
// CHECK-NEXT: |--ChoiceStartContentExpr `A` <col:3, col:4>
// CHECK-NEXT: `--ChoiceInnerContentExpr `B` <col:6, col:7>
* A[]B

View file

@ -0,0 +1,26 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:22, line:26>
// CHECK-NEXT: `--ChoiceStmt <line:22, line:26>
// CHECK-NEXT: |--ChoiceStarStmt <line:22, col:1:8>
// CHECK-NEXT: | `--ChoiceContentExpr <col:7, col:8>
// CHECK-NEXT: | `--ChoiceStartContentExpr `A` <col:7, col:8>
// CHECK-NEXT: |--ChoiceStarStmt <line:23, col:1:7>
// CHECK-NEXT: | `--ChoiceContentExpr <col:6, col:7>
// CHECK-NEXT: | `--ChoiceStartContentExpr `B` <col:6, col:7>
// CHECK-NEXT: |--ChoiceStarStmt <line:24, col:1:6>
// CHECK-NEXT: | `--ChoiceContentExpr <col:5, col:6>
// CHECK-NEXT: | `--ChoiceStartContentExpr `C` <col:5, col:6>
// CHECK-NEXT: |--ChoiceStarStmt <line:25, col:1:5>
// CHECK-NEXT: | `--ChoiceContentExpr <col:4, col:5>
// CHECK-NEXT: | `--ChoiceStartContentExpr `D` <col:4, col:5>
// CHECK-NEXT: `--ChoiceStarStmt <line:26, col:1:4>
// CHECK-NEXT: `--ChoiceContentExpr <col:3, col:4>
// CHECK-NEXT: `--ChoiceStartContentExpr `E` <col:3, col:4>
***** A
**** B
*** C
** D
* E

View file

@ -0,0 +1,27 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:23, line:27>
// CHECK-NEXT: |--TempDecl <line:23, col:3:13>
// CHECK-NEXT: | |--Identifier `x` <col:8, col:9>
// CHECK-NEXT: | `--NumberLiteral `2` <col:12, col:13>
// CHECK-NEXT: `--ContentStmt <line:25, col:1:48>
// CHECK-NEXT: `--Content <col:1, col:48>
// CHECK-NEXT: `--IfStmt <line:25, line:27>
// CHECK-NEXT: |--LogicalAndExpr <col:2, col:20>
// CHECK-NEXT: | |--LogicalGreaterThanOrEqualExpr <col:2, col:8>
// CHECK-NEXT: | | |--Identifier `x` <col:2, col:3>
// CHECK-NEXT: | | `--NumberLiteral `1` <col:7, col:8>
// CHECK-NEXT: | `--LogicalLesserThanOrEqualExpr <col:13, col:20>
// CHECK-NEXT: | |--Identifier `x` <col:13, col:14>
// CHECK-NEXT: | `--NumberLiteral `10` <col:18, col:20>
// CHECK-NEXT: `--BlockStmt <line:26, line:26>
// CHECK-NEXT: `--ContentStmt <line:26, col:5:25>
// CHECK-NEXT: `--Content <col:5, col:25>
// CHECK-NEXT: `--StringLiteral `Between one and ten!` <col:5, col:25>
~ temp x = 2
{x >= 1 and x <= 10:
Between one and ten!
}

View file

@ -0,0 +1,27 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:23, line:27>
// CHECK-NEXT: |--TempDecl <line:23, col:3:13>
// CHECK-NEXT: | |--Identifier `x` <col:8, col:9>
// CHECK-NEXT: | `--NumberLiteral `2` <col:12, col:13>
// CHECK-NEXT: `--ContentStmt <line:25, col:1:56>
// CHECK-NEXT: `--Content <col:1, col:56>
// CHECK-NEXT: `--IfStmt <line:25, line:27>
// CHECK-NEXT: |--LogicalOrExpr <col:2, col:22>
// CHECK-NEXT: | |--LogicalGreaterThanOrEqualExpr <col:2, col:8>
// CHECK-NEXT: | | |--Identifier `x` <col:2, col:3>
// CHECK-NEXT: | | `--NumberLiteral `1` <col:7, col:8>
// CHECK-NEXT: | `--LogicalEqualityExpr <col:12, col:22>
// CHECK-NEXT: | |--Identifier `x` <col:12, col:13>
// CHECK-NEXT: | `--FalseLiteral <col:17, col:22>
// CHECK-NEXT: `--BlockStmt <line:26, line:26>
// CHECK-NEXT: `--ContentStmt <line:26, col:5:31>
// CHECK-NEXT: `--Content <col:5, col:31>
// CHECK-NEXT: `--StringLiteral `Greater than one or false!` <col:5, col:31>
~ temp x = 2
{x >= 1 or x == false:
Greater than one or false!
}

View file

@ -0,0 +1,23 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:19, line:23>
// CHECK-NEXT: `--ContentStmt <line:19, col:1:52>
// CHECK-NEXT: `--Content <col:1, col:52>
// CHECK-NEXT: `--IfStmt <line:19, line:23>
// CHECK-NEXT: |--TrueLiteral <col:2, col:6>
// CHECK-NEXT: |--BlockStmt <line:20, line:20>
// CHECK-NEXT: | `--ContentStmt <line:20, col:5:18>
// CHECK-NEXT: | `--Content <col:5, col:18>
// CHECK-NEXT: | `--StringLiteral `Hello, world!` <col:5, col:18>
// CHECK-NEXT: `--ElseBranch <col:3, col:13>
// CHECK-NEXT: `--BlockStmt <line:22, line:22>
// CHECK-NEXT: `--ContentStmt <line:22, col:5:17>
// CHECK-NEXT: `--Content <col:5, col:17>
// CHECK-NEXT: `--StringLiteral `Unreachable!` <col:5, col:17>
{true:
Hello, world!
- else:
Unreachable!
}

View file

@ -0,0 +1,16 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:14, line:16>
// CHECK-NEXT: `--ContentStmt <line:14, col:1:27>
// CHECK-NEXT: `--Content <col:1, col:27>
// CHECK-NEXT: `--IfStmt <line:14, line:16>
// CHECK-NEXT: |--TrueLiteral <col:2, col:6>
// CHECK-NEXT: `--BlockStmt <line:15, line:15>
// CHECK-NEXT: `--ContentStmt <line:15, col:5:18>
// CHECK-NEXT: `--Content <col:5, col:18>
// CHECK-NEXT: `--StringLiteral `Hello, world!` <col:5, col:18>
{true:
Hello, world!
}

View file

@ -0,0 +1,43 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:36, line:43>
// CHECK-NEXT: |--TempDecl <line:36, col:3:13>
// CHECK-NEXT: | |--Identifier `x` <col:8, col:9>
// CHECK-NEXT: | `--NumberLiteral `3` <col:12, col:13>
// CHECK-NEXT: `--ContentStmt <line:38, col:1:51>
// CHECK-NEXT: `--Content <col:1, col:51>
// CHECK-NEXT: `--SwitchStmt <line:38, line:43>
// CHECK-NEXT: |--Identifier `x` <col:3, col:4>
// CHECK-NEXT: |--SwitchCase <col:3, col:7>
// CHECK-NEXT: | |--NumberLiteral `0` <col:3, col:4>
// CHECK-NEXT: | `--BlockStmt <line:39, line:39>
// CHECK-NEXT: | `--ContentStmt <line:39, col:7:11>
// CHECK-NEXT: | `--Content <col:7, col:11>
// CHECK-NEXT: | `--StringLiteral `zero` <col:7, col:11>
// CHECK-NEXT: |--SwitchCase <col:3, col:7>
// CHECK-NEXT: | |--NumberLiteral `1` <col:3, col:4>
// CHECK-NEXT: | `--BlockStmt <line:40, line:40>
// CHECK-NEXT: | `--ContentStmt <line:40, col:7:10>
// CHECK-NEXT: | `--Content <col:7, col:10>
// CHECK-NEXT: | `--StringLiteral `one` <col:7, col:10>
// CHECK-NEXT: |--SwitchCase <col:3, col:7>
// CHECK-NEXT: | |--NumberLiteral `2` <col:3, col:4>
// CHECK-NEXT: | `--BlockStmt <line:41, line:41>
// CHECK-NEXT: | `--ContentStmt <line:41, col:7:10>
// CHECK-NEXT: | `--Content <col:7, col:10>
// CHECK-NEXT: | `--StringLiteral `two` <col:7, col:10>
// CHECK-NEXT: `--ElseBranch <col:3, col:9>
// CHECK-NEXT: `--BlockStmt <line:42, line:42>
// CHECK-NEXT: `--ContentStmt <line:42, col:9:13>
// CHECK-NEXT: `--Content <col:9, col:13>
// CHECK-NEXT: `--StringLiteral `lots` <col:9, col:13>
~ temp x = 3
{ x:
- 0: zero
- 1: one
- 2: two
- else: lots
}

View file

@ -0,0 +1 @@
{true: Hello world!}

View file

@ -0,0 +1 @@
CONST foo =

View file

@ -0,0 +1,23 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:21, line:23>
// CHECK-NEXT: |--VarDecl <line:21, col:1:24>
// CHECK-NEXT: | |--Identifier `x` <col:5, col:6>
// CHECK-NEXT: | `--StringExpr `"Hello world 1"` <col:9, col:24>
// CHECK-NEXT: | `--StringLiteral `Hello world 1` <col:10, col:23>
// CHECK-NEXT: |--ContentStmt <line:22, col:1:4>
// CHECK-NEXT: | `--Content <col:1, col:4>
// CHECK-NEXT: | `--InlineLogicExpr <col:1, col:4>
// CHECK-NEXT: | `--Identifier `x` <col:2, col:3>
// CHECK-NEXT: `--ContentStmt <line:23, col:1:19>
// CHECK-NEXT: `--Content <col:1, col:19>
// CHECK-NEXT: |--StringLiteral `Hello ` <col:1, col:7>
// CHECK-NEXT: |--InlineLogicExpr <col:7, col:16>
// CHECK-NEXT: | `--StringExpr `"world"` <col:8, col:15>
// CHECK-NEXT: | `--StringLiteral `world` <col:9, col:14>
// CHECK-NEXT: `--StringLiteral ` 2.` <col:16, col:19>
VAR x = "Hello world 1"
{x}
Hello {"world"} 2.

View file

@ -0,0 +1,9 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:9, line:9>
// CHECK-NEXT: `--ChoiceStmt <line:9, line:9>
// CHECK-NEXT: `--ChoiceStarStmt <line:9, col:1:2>
// CHECK-NEXT: `--ChoiceContentExpr <col:2, col:2>
*

View file

@ -0,0 +1,3 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"

View file

@ -0,0 +1,12 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: |--KnotDecl <line:11, line:11>
// CHECK-NEXT: | `--KnotProto <col:1, col:5>
// CHECK-NEXT: | `--Identifier `a` <col:4, col:5>
// CHECK-NEXT: `--KnotDecl <line:12, line:12>
// CHECK-NEXT: `--KnotProto <col:1, col:5>
// CHECK-NEXT: `--Identifier `b` <col:4, col:5>
== a
== b

View file

@ -0,0 +1 @@
~

View file

@ -0,0 +1,16 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:16, line:16>
// CHECK-NEXT: `--ExprStmt <line:16, col:3:20>
// CHECK-NEXT: `--SubtractExpr <col:4, col:20>
// CHECK-NEXT: |--MultiplyExpr <col:4, col:15>
// CHECK-NEXT: | |--AddExpr <col:4, col:10>
// CHECK-NEXT: | | |--NegateExpr <col:4, col:6>
// CHECK-NEXT: | | | `--NumberLiteral `1` <col:5, col:6>
// CHECK-NEXT: | | `--NumberLiteral `2` <col:9, col:10>
// CHECK-NEXT: | `--NumberLiteral `3` <col:14, col:15>
// CHECK-NEXT: `--NegateExpr <col:18, col:20>
// CHECK-NEXT: `--NumberLiteral `4` <col:19, col:20>
~ (-1 + 2) * 3 - -4

View file

@ -0,0 +1,46 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: |--BlockStmt <line:39, line:39>
// CHECK-NEXT: | `--ContentStmt <line:39, col:1:18>
// CHECK-NEXT: | `--Content <col:1, col:18>
// CHECK-NEXT: | `--InlineLogicExpr <col:1, col:18>
// CHECK-NEXT: | `--CallExpr <col:3, col:17>
// CHECK-NEXT: | |--Identifier `factorial` <col:3, col:12>
// CHECK-NEXT: | `--ArgumentList <col:12, col:17>
// CHECK-NEXT: | `--NumberLiteral `10` <col:13, col:15>
// CHECK-NEXT: `--FunctionDecl <line:41, line:46>
// CHECK-NEXT: |--FunctionProto <col:1, col:25>
// CHECK-NEXT: | |--Identifier `factorial` <col:13, col:22>
// CHECK-NEXT: | `--ParamList <col:22, col:25>
// CHECK-NEXT: | `--ParamDecl `n` <col:23, col:24>
// CHECK-NEXT: `--BlockStmt <line:42, line:46>
// CHECK-NEXT: `--ContentStmt <line:42, col:3:83>
// CHECK-NEXT: `--Content <col:3, col:83>
// CHECK-NEXT: `--IfStmt <line:42, line:46>
// CHECK-NEXT: |--LogicalEqualityExpr <col:5, col:11>
// CHECK-NEXT: | |--Identifier `n` <col:5, col:6>
// CHECK-NEXT: | `--NumberLiteral `1` <col:10, col:11>
// CHECK-NEXT: |--BlockStmt <line:43, line:43>
// CHECK-NEXT: | `--ReturnStmt <line:43, col:9:17>
// CHECK-NEXT: | `--NumberLiteral `1` <col:16, col:17>
// CHECK-NEXT: `--ElseBranch <col:7, col:19>
// CHECK-NEXT: `--BlockStmt <line:45, line:45>
// CHECK-NEXT: `--ReturnStmt <line:45, col:9:38>
// CHECK-NEXT: `--MultiplyExpr <col:17, col:37>
// CHECK-NEXT: |--Identifier `n` <col:17, col:18>
// CHECK-NEXT: `--CallExpr <col:21, col:37>
// CHECK-NEXT: |--Identifier `factorial` <col:21, col:30>
// CHECK-NEXT: `--ArgumentList <col:30, col:37>
// CHECK-NEXT: `--SubtractExpr <col:31, col:36>
// CHECK-NEXT: |--Identifier `n` <col:31, col:32>
// CHECK-NEXT: `--NumberLiteral `1` <col:35, col:36>
{ factorial(10) }
== function factorial(n)
{ n == 1:
~ return 1
- else:
~ return (n * factorial(n - 1))
}

View file

@ -0,0 +1,62 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: |--BlockStmt <line:52, line:52>
// CHECK-NEXT: | `--ContentStmt <line:52, col:1:12>
// CHECK-NEXT: | `--Content <col:1, col:12>
// CHECK-NEXT: | `--InlineLogicExpr <col:1, col:12>
// CHECK-NEXT: | `--CallExpr <col:3, col:11>
// CHECK-NEXT: | |--Identifier `fib` <col:3, col:6>
// CHECK-NEXT: | `--ArgumentList <col:6, col:11>
// CHECK-NEXT: | `--NumberLiteral `10` <col:7, col:9>
// CHECK-NEXT: `--FunctionDecl <line:54, line:62>
// CHECK-NEXT: |--FunctionProto <col:1, col:19>
// CHECK-NEXT: | |--Identifier `fib` <col:13, col:16>
// CHECK-NEXT: | `--ParamList <col:16, col:19>
// CHECK-NEXT: | `--ParamDecl `n` <col:17, col:18>
// CHECK-NEXT: `--BlockStmt <line:55, line:62>
// CHECK-NEXT: `--ContentStmt <line:55, col:3:121>
// CHECK-NEXT: `--Content <col:3, col:121>
// CHECK-NEXT: `--MultiIfStmt <line:55, line:62>
// CHECK-NEXT: |--IfBranch <col:7, col:21>
// CHECK-NEXT: | |--LogicalEqualityExpr <col:7, col:13>
// CHECK-NEXT: | | |--Identifier `n` <col:7, col:8>
// CHECK-NEXT: | | `--NumberLiteral `0` <col:12, col:13>
// CHECK-NEXT: | `--BlockStmt <line:57, line:57>
// CHECK-NEXT: | `--ReturnStmt <line:57, col:9:17>
// CHECK-NEXT: | `--NumberLiteral `0` <col:16, col:17>
// CHECK-NEXT: |--IfBranch <col:7, col:21>
// CHECK-NEXT: | |--LogicalEqualityExpr <col:7, col:13>
// CHECK-NEXT: | | |--Identifier `n` <col:7, col:8>
// CHECK-NEXT: | | `--NumberLiteral `1` <col:12, col:13>
// CHECK-NEXT: | `--BlockStmt <line:59, line:59>
// CHECK-NEXT: | `--ReturnStmt <line:59, col:9:17>
// CHECK-NEXT: | `--NumberLiteral `1` <col:16, col:17>
// CHECK-NEXT: `--ElseBranch <col:7, col:19>
// CHECK-NEXT: `--BlockStmt <line:61, line:61>
// CHECK-NEXT: `--ReturnStmt <line:61, col:9:39>
// CHECK-NEXT: `--AddExpr <col:16, col:39>
// CHECK-NEXT: |--CallExpr <col:16, col:27>
// CHECK-NEXT: | |--Identifier `fib` <col:16, col:19>
// CHECK-NEXT: | `--ArgumentList <col:19, col:27>
// CHECK-NEXT: | `--SubtractExpr <col:20, col:25>
// CHECK-NEXT: | |--Identifier `n` <col:20, col:21>
// CHECK-NEXT: | `--NumberLiteral `1` <col:24, col:25>
// CHECK-NEXT: `--CallExpr <col:29, col:39>
// CHECK-NEXT: |--Identifier `fib` <col:29, col:32>
// CHECK-NEXT: `--ArgumentList <col:32, col:39>
// CHECK-NEXT: `--SubtractExpr <col:33, col:38>
// CHECK-NEXT: |--Identifier `n` <col:33, col:34>
// CHECK-NEXT: `--NumberLiteral `2` <col:37, col:38>
{ fib(10) }
== function fib(n)
{
- n == 0:
~ return 0
- n == 1:
~ return 1
- else:
~ return fib(n - 1) + fib(n - 2)
}

View file

@ -0,0 +1,28 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: |--BlockStmt <line:25, line:25>
// CHECK-NEXT: | `--ContentStmt <line:25, col:1:13>
// CHECK-NEXT: | `--Content <col:1, col:13>
// CHECK-NEXT: | `--InlineLogicExpr <col:1, col:13>
// CHECK-NEXT: | `--CallExpr <col:2, col:12>
// CHECK-NEXT: | |--Identifier `func` <col:2, col:6>
// CHECK-NEXT: | `--ArgumentList <col:6, col:12>
// CHECK-NEXT: | |--NumberLiteral `1` <col:7, col:8>
// CHECK-NEXT: | `--NumberLiteral `2` <col:10, col:11>
// CHECK-NEXT: `--FunctionDecl <line:27, line:28>
// CHECK-NEXT: |--FunctionProto <col:1, col:23>
// CHECK-NEXT: | |--Identifier `func` <col:13, col:17>
// CHECK-NEXT: | `--ParamList <col:17, col:23>
// CHECK-NEXT: | |--ParamDecl `a` <col:18, col:19>
// CHECK-NEXT: | `--ParamDecl `b` <col:21, col:22>
// CHECK-NEXT: `--BlockStmt <line:28, line:28>
// CHECK-NEXT: `--ReturnStmt <line:28, col:3:15>
// CHECK-NEXT: `--AddExpr <col:10, col:15>
// CHECK-NEXT: |--Identifier `a` <col:10, col:11>
// CHECK-NEXT: `--Identifier `b` <col:14, col:15>
{func(1, 2)}
== function func(a, b)
~ return a + b

View file

@ -0,0 +1,9 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:9, line:9>
// CHECK-NEXT: `--ContentStmt <line:9, col:1:14>
// CHECK-NEXT: `--Content <col:1, col:14>
// CHECK-NEXT: `--StringLiteral `Hello, world!` <col:1, col:14>
Hello, world!

View file

@ -0,0 +1,2 @@
===
Hello, world!

View file

@ -0,0 +1,34 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: |--BlockStmt <line:30, line:30>
// CHECK-NEXT: | `--DivertStmt <line:30, col:1:16>
// CHECK-NEXT: | `--Divert <col:1, col:16>
// CHECK-NEXT: | `--CallExpr <col:4, col:16>
// CHECK-NEXT: | |--SelectorExpr <col:4, col:7>
// CHECK-NEXT: | | |--Identifier `a` <col:4, col:5>
// CHECK-NEXT: | | `--Identifier `b` <col:6, col:7>
// CHECK-NEXT: | `--ArgumentList <col:7, col:16>
// CHECK-NEXT: | `--StringExpr `"Brett"` <col:8, col:15>
// CHECK-NEXT: | `--StringLiteral `Brett` <col:9, col:14>
// CHECK-NEXT: `--KnotDecl <line:32, line:34>
// CHECK-NEXT: |--KnotProto <col:1, col:5>
// CHECK-NEXT: | `--Identifier `a` <col:4, col:5>
// CHECK-NEXT: `--StitchDecl <line:33, line:34>
// CHECK-NEXT: |--StitchProto <col:1, col:10>
// CHECK-NEXT: | |--Identifier `b` <col:3, col:4>
// CHECK-NEXT: | `--ParamList <col:4, col:10>
// CHECK-NEXT: | `--ParamDecl `name` <col:5, col:9>
// CHECK-NEXT: `--BlockStmt <line:34, line:34>
// CHECK-NEXT: `--ContentStmt <line:34, col:1:15>
// CHECK-NEXT: `--Content <col:1, col:15>
// CHECK-NEXT: |--StringLiteral `Hello, ` <col:1, col:8>
// CHECK-NEXT: |--InlineLogicExpr <col:8, col:14>
// CHECK-NEXT: | `--Identifier `name` <col:9, col:13>
// CHECK-NEXT: `--StringLiteral `!` <col:14, col:15>
-> a.b("Brett")
== a
= b(name)
Hello, {name}!

View file

@ -0,0 +1,31 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--KnotDecl <line:26, line:31>
// CHECK-NEXT: |--KnotProto <col:1, col:13>
// CHECK-NEXT: | `--Identifier `knot` <col:5, col:9>
// CHECK-NEXT: |--BlockStmt <line:27, line:27>
// CHECK-NEXT: | `--ContentStmt <line:27, col:1:19>
// CHECK-NEXT: | `--Content <col:1, col:19>
// CHECK-NEXT: | `--StringLiteral `Hello from `knot`!` <col:1, col:19>
// CHECK-NEXT: |--StitchDecl <line:28, line:29>
// CHECK-NEXT: | |--StitchProto <col:1, col:4>
// CHECK-NEXT: | | `--Identifier `a` <col:3, col:4>
// CHECK-NEXT: | `--BlockStmt <line:29, line:29>
// CHECK-NEXT: | `--ContentStmt <line:29, col:1:21>
// CHECK-NEXT: | `--Content <col:1, col:21>
// CHECK-NEXT: | `--StringLiteral `Hello from `knot.a`!` <col:1, col:21>
// CHECK-NEXT: `--StitchDecl <line:30, line:31>
// CHECK-NEXT: |--StitchProto <col:1, col:4>
// CHECK-NEXT: | `--Identifier `b` <col:3, col:4>
// CHECK-NEXT: `--BlockStmt <line:31, line:31>
// CHECK-NEXT: `--ContentStmt <line:31, col:1:21>
// CHECK-NEXT: `--Content <col:1, col:21>
// CHECK-NEXT: `--StringLiteral `Hello from `knot.b`!` <col:1, col:21>
=== knot ===
Hello from `knot`!
= a
Hello from `knot.a`!
= b
Hello from `knot.b`!

View file

@ -0,0 +1,17 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--KnotDecl <line:15, line:17>
// CHECK-NEXT: |--KnotProto <col:1, col:13>
// CHECK-NEXT: | `--Identifier `knot` <col:5, col:9>
// CHECK-NEXT: `--StitchDecl <line:16, line:17>
// CHECK-NEXT: |--StitchProto <col:1, col:9>
// CHECK-NEXT: | `--Identifier `stitch` <col:3, col:9>
// CHECK-NEXT: `--BlockStmt <line:17, line:17>
// CHECK-NEXT: `--ContentStmt <line:17, col:1:14>
// CHECK-NEXT: `--Content <col:1, col:14>
// CHECK-NEXT: `--StringLiteral `Hello, world!` <col:1, col:14>
=== knot ===
= stitch
Hello, world!

View file

@ -0,0 +1,13 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:12, line:13>
// CHECK-NEXT: |--TempDecl <line:12, col:3:15>
// CHECK-NEXT: | |--Identifier `a` <col:8, col:9>
// CHECK-NEXT: | `--NumberLiteral `123` <col:12, col:15>
// CHECK-NEXT: `--AssignStmt <line:13, col:3:10>
// CHECK-NEXT: |--Identifier `a` <col:3, col:4>
// CHECK-NEXT: `--NumberLiteral `321` <col:7, col:10>
~ temp a = 123
~ a = 321

View file

@ -0,0 +1,18 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--BlockStmt <line:16, line:18>
// CHECK-NEXT: `--ChoiceStmt <line:16, line:18>
// CHECK-NEXT: |--ChoiceStarStmt <line:16, col:1:4>
// CHECK-NEXT: | `--ChoiceContentExpr <col:3, col:4>
// CHECK-NEXT: | `--ChoiceStartContentExpr `A` <col:3, col:4>
// CHECK-NEXT: |--ChoiceStarStmt <line:17, col:1:4>
// CHECK-NEXT: | `--ChoiceContentExpr <col:3, col:4>
// CHECK-NEXT: | `--ChoiceStartContentExpr `B` <col:3, col:4>
// CHECK-NEXT: `--ChoiceStarStmt <line:18, col:1:4>
// CHECK-NEXT: `--ChoiceContentExpr <col:3, col:4>
// CHECK-NEXT: `--ChoiceStartContentExpr `C` <col:3, col:4>
* A
* B
* C

View file

@ -0,0 +1,13 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--KnotDecl <line:12, line:13>
// CHECK-NEXT: |--KnotProto <col:1, col:13>
// CHECK-NEXT: | `--Identifier `knot` <col:5, col:9>
// CHECK-NEXT: `--BlockStmt <line:13, line:13>
// CHECK-NEXT: `--ContentStmt <line:13, col:1:14>
// CHECK-NEXT: `--Content <col:1, col:14>
// CHECK-NEXT: `--StringLiteral `Hello, world!` <col:1, col:14>
=== knot ===
Hello, world!

View file

@ -0,0 +1,13 @@
// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s
// CHECK: File "<STDIN>"
// CHECK-NEXT: `--StitchDecl <line:12, line:13>
// CHECK-NEXT: |--StitchProto <col:1, col:9>
// CHECK-NEXT: | `--Identifier `stitch` <col:3, col:9>
// CHECK-NEXT: `--BlockStmt <line:13, line:13>
// CHECK-NEXT: `--ContentStmt <line:13, col:1:14>
// CHECK-NEXT: `--Content <col:1, col:14>
// CHECK-NEXT: `--StringLiteral `Hello, world!` <col:1, col:14>
= stitch
Hello, world!

View file

@ -0,0 +1 @@
VAR foo =