From 619eb3b3384827a8c483ab5fa1a97b5a6aca1eec Mon Sep 17 00:00:00 2001 From: Brett Broadhurst Date: Fri, 27 Feb 2026 18:07:02 -0700 Subject: [PATCH] feat: improved parsing and regression test suite --- .gitignore | 6 +- CMakeLists.txt | 5 + build.zig | 1 - src/Ast.zig | 215 +++--- src/Ast/Render.zig | 18 +- src/Parse.zig | 622 ++++++++++++------ src/main.zig | 19 +- src/root.zig | 12 +- testing/regression/CMakeLists.txt | 9 + testing/regression/lit.cfg.py | 14 + testing/regression/lit.site.cfg.py.in | 8 + .../regression/syntax/choice-branch-mixed.ink | 11 + .../syntax/choice-nesting-normalize.ink | 26 + .../syntax/conditional-logical-and.ink | 27 + .../syntax/conditional-logical-or.ink | 27 + .../syntax/conditional-simple-if-else.ink | 23 + .../syntax/conditional-simple-if.ink | 16 + .../syntax/conditional-simple-switch.ink | 43 ++ .../syntax/conditional-substitution.ink | 1 + .../syntax/const-expected-expression.ink | 1 + .../syntax/content-substitution.ink | 23 + .../regression/syntax/empty-choice-branch.ink | 9 + testing/regression/syntax/empty-file.ink | 3 + testing/regression/syntax/empty-knots.ink | 12 + testing/regression/syntax/empty-logic.ink | 1 + .../syntax/expression-statement.ink | 16 + testing/regression/syntax/factorial.ink | 46 ++ testing/regression/syntax/fibonacci.ink | 62 ++ testing/regression/syntax/function-simple.ink | 28 + testing/regression/syntax/hello-world.ink | 9 + .../syntax/knot-expected-identifier.ink | 2 + .../syntax/knot-qualified-lookup.ink | 34 + testing/regression/syntax/knot-stitches.ink | 31 + testing/regression/syntax/nested-stitch.ink | 17 + .../regression/syntax/simple-assignment.ink | 13 + testing/regression/syntax/simple-choice.ink | 18 + testing/regression/syntax/simple-knot.ink | 13 + testing/regression/syntax/simple-stitch.ink | 13 + .../syntax/var-expected-expression.ink | 1 + 39 files changed, 1116 insertions(+), 339 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 testing/regression/CMakeLists.txt create mode 100644 testing/regression/lit.cfg.py create mode 100644 testing/regression/lit.site.cfg.py.in create mode 100644 testing/regression/syntax/choice-branch-mixed.ink create mode 100644 testing/regression/syntax/choice-nesting-normalize.ink create mode 100644 testing/regression/syntax/conditional-logical-and.ink create mode 100644 testing/regression/syntax/conditional-logical-or.ink create mode 100644 testing/regression/syntax/conditional-simple-if-else.ink create mode 100644 testing/regression/syntax/conditional-simple-if.ink create mode 100644 testing/regression/syntax/conditional-simple-switch.ink create mode 100644 testing/regression/syntax/conditional-substitution.ink create mode 100644 testing/regression/syntax/const-expected-expression.ink create mode 100644 testing/regression/syntax/content-substitution.ink create mode 100644 testing/regression/syntax/empty-choice-branch.ink create mode 100644 testing/regression/syntax/empty-file.ink create mode 100644 testing/regression/syntax/empty-knots.ink create mode 100644 testing/regression/syntax/empty-logic.ink create mode 100644 testing/regression/syntax/expression-statement.ink create mode 100644 testing/regression/syntax/factorial.ink create mode 100644 testing/regression/syntax/fibonacci.ink create mode 100644 testing/regression/syntax/function-simple.ink create mode 100644 testing/regression/syntax/hello-world.ink create mode 100644 testing/regression/syntax/knot-expected-identifier.ink create mode 100644 testing/regression/syntax/knot-qualified-lookup.ink create mode 100644 testing/regression/syntax/knot-stitches.ink create mode 100644 testing/regression/syntax/nested-stitch.ink create mode 100644 testing/regression/syntax/simple-assignment.ink create mode 100644 testing/regression/syntax/simple-choice.ink create mode 100644 testing/regression/syntax/simple-knot.ink create mode 100644 testing/regression/syntax/simple-stitch.ink create mode 100644 testing/regression/syntax/var-expected-expression.ink diff --git a/.gitignore b/.gitignore index 0097270..9011683 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,10 @@ # Directories .cache/ -.zig-cache -zig-out +.zig-cache/ +zig-out/ +build/ +build-* # Temporary Files / Logs *.log diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0872fb9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.20) + +project(InkCompiler LANGUAGES NONE) + +add_subdirectory(testing/regression) diff --git a/build.zig b/build.zig index 4cdb95b..6d6eb72 100644 --- a/build.zig +++ b/build.zig @@ -7,7 +7,6 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/root.zig"), .target = target, }); - const exe = b.addExecutable(.{ .name = "inkc", .root_module = b.createModule(.{ diff --git a/src/Ast.zig b/src/Ast.zig index 23ab7c1..078609e 100644 --- a/src/Ast.zig +++ b/src/Ast.zig @@ -12,10 +12,14 @@ errors: []Error, pub const Node = struct { tag: Tag, - source_start: usize, - source_end: usize, + loc: Span, data: Data, + pub const Span = struct { + start: usize, + end: usize, + }; + pub const Tag = enum { file, false_literal, @@ -107,6 +111,98 @@ pub const Node = struct { 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 { @@ -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( gpa: std.mem.Allocator, arena: std.mem.Allocator, diff --git a/src/Ast/Render.zig b/src/Ast/Render.zig index 57ccf53..7bdbb48 100644 --- a/src/Ast/Render.zig +++ b/src/Ast/Render.zig @@ -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 { 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 writer.print("`{s}`", .{lexeme}); try r.tty_config.setColor(writer, .reset); } fn writeLineSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void { - const line_start = r.lines.calculateLine(node.source_start); - const line_end = r.lines.calculateLine(node.source_end); + const line_start = r.lines.calculateLine(node.loc.start); + const line_end = r.lines.calculateLine(node.loc.end); try r.tty_config.setColor(writer, .white); 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 { - 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 column_start = (node.source_start - line_range.start); - const column_end = (node.source_end - line_range.start); + const column_start = (node.loc.start - line_range.start); + const column_end = (node.loc.end - line_range.start); try r.tty_config.setColor(writer, .white); 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 { - 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 column_start = (node.source_start - line_range.start); - const column_end = (node.source_end - line_range.start); + const column_start = (node.loc.start - line_range.start); + const column_end = (node.loc.end - line_range.start); try r.tty_config.setColor(writer, .white); try writer.writeByte('<'); diff --git a/src/Parse.zig b/src/Parse.zig index 70b87d9..c78da5b 100644 --- a/src/Parse.zig +++ b/src/Parse.zig @@ -1,10 +1,9 @@ const std = @import("std"); -const assert = std.debug.assert; const tok = @import("tokenizer.zig"); +const Ast = @import("Ast.zig"); +const assert = std.debug.assert; const Token = tok.Token; const Tokenizer = tok.Tokenizer; -const Ast = @import("Ast.zig"); - const Parse = @This(); gpa: std.mem.Allocator, @@ -99,7 +98,7 @@ fn getTokenInfixType(tag: Token.Tag) Ast.Node.Tag { .star => .multiply_expr, .slash => .divide_expr, // .question_mark => .contains_expr, - .equal => .assign_stmt, + // .equal => .assign_stmt, .ampersand_ampersand, .keyword_and => .logical_and_expr, .pipe_pipe, .keyword_or => .logical_or_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 { assert(p.grammar_stack.items.len > 0); - const token = p.token; const context = p.grammar_stack.getLast(); 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; assert(span > 0); + const list = try p.arena.alloc(*Ast.Node, span); 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]; li += 1; } - return list; } @@ -269,15 +267,14 @@ fn makeNodeSequence( p: *Parse, context: *const StmtContext, tag: Ast.Node.Tag, - bytes_start: usize, - bytes_end: usize, + loc: Ast.Node.Span, scratch_offset: usize, ) Error!*Ast.Node { var list: ?[]*Ast.Node = null; if (!p.isScratchEmpty(context)) { 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 { @@ -337,23 +334,20 @@ fn collectBlock(p: *Parse, context: *StmtContext, level: usize) Error!?*Ast.Node const block = p.peekBlockStack(context); if (block.level < level) return null; - const bytes_start = block.source_offset; - var bytes_end: usize = 0; + const span_start = block.source_offset; + var span_end: usize = 0; if (!p.isScratchEmpty(context)) { const last = p.peekScratch(context); - bytes_end = last.source_end; + span_end = last.loc.end; } else { - bytes_end = bytes_start; + span_end = span_start; } - var node = try p.makeNodeSequence( - context, - .block_stmt, - bytes_start, - bytes_end, - block.scratch_offset, - ); + var node = try p.makeNodeSequence(context, .block_stmt, .{ + .start = span_start, + .end = span_end, + }, block.scratch_offset); node = try p.fixupBlock(context, node); _ = p.popBlockStack(context); return node; @@ -407,13 +401,10 @@ fn collectContext( } } - const node = try p.makeNodeSequence( - context, - .choice_stmt, - choice_state.source_offset, - p.token.loc.start, - choice_state.scratch_offset, - ); + const node = try p.makeNodeSequence(context, .choice_stmt, .{ + .start = choice_state.source_offset, + .end = p.token.loc.start, + }, choice_state.scratch_offset); try p.scratch.append(p.gpa, node); } 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, 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); - 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 { @@ -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); defer _ = p.popScratch(context); - const bytes_start = proto.source_start; - const bytes_end = if (child) |n| n.source_end else proto.source_end; - - return Ast.createKnotDeclNode(p.arena, .knot_decl, bytes_start, bytes_end, proto, list); + return .createKnot(p.arena, .knot_decl, .{ + .start = proto.loc.start, + .end = if (child) |n| n.loc.end else proto.loc.end, + }, proto, list); } 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, .{ .level = 0, .scratch_offset = p.scratch.items.len, - .source_offset = node.source_start, + .source_offset = node.loc.start, }); } if (p.isChoiceStackEmpty(context)) { try p.choice_stack.append(p.gpa, .{ .level = level, .scratch_offset = p.scratch.items.len, - .source_offset = node.source_start, + .source_offset = node.loc.start, }); } else { 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, }); } - try p.choice_stack.append(p.gpa, .{ .level = level, .scratch_offset = p.scratch.items.len, - .source_offset = node.source_start, + .source_offset = node.loc.start, }); } else if (level == choice_state.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 { - const token = p.token; + const main_token = p.token; const level = context.level; if (p.isBlockStackEmpty(context)) { assert(p.isChoiceStackEmpty(context)); - try p.block_stack.append(p.gpa, .{ .level = 0, .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. @@ -522,20 +512,16 @@ fn handleGatherPoint(p: *Parse, context: *StmtContext, node: **Ast.Node) !void { try p.block_stack.append(p.gpa, .{ .level = choice_state.level, .scratch_offset = p.scratch.items.len, - .source_offset = node.*.source_start, + .source_offset = node.*.loc.start, }); } } else if (!p.isScratchEmpty(context)) { const tmp = (try p.collectContext(context, level - 1, true)) orelse @panic("FUCK!"); if (tmp.tag == .choice_stmt) { - node.* = try Ast.createBinaryNode( - p.arena, - .gathered_stmt, - tmp.source_start, - token.loc.start, - tmp, - node.*, - ); + node.* = try Ast.Node.createBinary(p.arena, .gathered_stmt, .{ + .start = tmp.loc.start, + .end = main_token.loc.start, + }, tmp, node.*); } if (!p.isBlockStackEmpty(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, .{ .level = 0, .scratch_offset = p.scratch.items.len, - .source_offset = node.source_start, + .source_offset = node.loc.start, }); } 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, .{ .level = choice_state.level, .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 { - const token = p.nextToken(); - const span_start = token.loc.start; - const span_end = token.loc.end; - return Ast.createLeafNode(p.arena, tag, span_start, span_end); + const main_token = p.nextToken(); + return .createLeaf(p.arena, tag, .{ + .start = main_token.loc.start, + .end = main_token.loc.end, + }); } 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 { switch (p.token.tag) { .keyword_not, .minus, .exclaimation_mark => { - const token = p.nextToken(); + const main_token = p.nextToken(); const lhs = try parsePrefixExpr(p); if (lhs == null) return null; - const tag = getTokenPrefixType(token.tag); - const bytes_start = token.loc.start; - const bytes_end = if (lhs) |n| n.source_end else p.token.loc.end; - return Ast.createBinaryNode(p.arena, tag, bytes_start, bytes_end, lhs, null); + const tag = getTokenPrefixType(main_token.tag); + return .createBinary(p.arena, tag, .{ + .start = main_token.loc.start, + .end = if (lhs) |n| n.loc.end else p.token.loc.end, + }, lhs, null); }, else => return parsePrimaryExpr(p), } @@ -673,10 +661,10 @@ fn parseInfixExpr( next_node = try parseInfixExpr(p, null, token_precedence); if (next_node) |rhs| { const tag = getTokenInfixType(token.tag); - const bytes_start = if (lhs) |n| n.source_start else token.loc.start; - const bytes_end = rhs.source_end; - - lhs = try Ast.createBinaryNode(p.arena, tag, bytes_start, bytes_end, lhs, rhs); + lhs = try Ast.Node.createBinary(p.arena, tag, .{ + .start = if (lhs) |n| n.loc.start else token.loc.start, + .end = rhs.loc.end, + }, lhs, rhs); } else return null; } else break; } @@ -689,7 +677,7 @@ fn parseExpression(p: *Parse) Error!?*Ast.Node { fn parseStringExpr(p: *Parse) Error!*Ast.Node { assert(p.token.tag == .double_quote); - const leading_token = p.nextToken(); + const main_token = p.nextToken(); while (true) switch (p.token.tag) { .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 span_start = leading_token.loc.start; - const span_end = p.token.loc.start; - const expr = try Ast.createLeafNode(p.arena, .string_literal, leading_token.loc.end, last_token.loc.start); - return Ast.createBinaryNode(p.arena, .string_expr, span_start, span_end, expr, null); + const expr = try Ast.Node.createLeaf(p.arena, .string_literal, .{ + .start = main_token.loc.end, + .end = last_token.loc.start, + }); + 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 { const main_token = p.token; while (!p.checkTokenInSet(token_set)) _ = p.nextToken(); - const span_start = main_token.loc.start; - const span_end = p.token.loc.start; - const tag: Ast.Node.Tag = if (span_start == span_end) .empty_string else .string_literal; - return Ast.createLeafNode(p.arena, tag, span_start, span_end); + return .createLeaf(p.arena, if (main_token.loc.start == p.token.loc.start) + .empty_string + else + .string_literal, .{ + .start = main_token.loc.start, + .end = p.token.loc.start, + }); } 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); _ = try p.expectNewline(); - const bytes_start = if (lhs) |n| n.source_start else token.loc.start; - const bytes_end = p.token.loc.start; - return Ast.createBinaryNode(p.arena, .expr_stmt, bytes_start, bytes_end, node, null); + return .createBinary(p.arena, .expr_stmt, .{ + .start = if (lhs) |n| n.loc.start else main_token.loc.start, + .end = p.token.loc.start, + }, node, null); } fn parseAssignStmt(p: *Parse) Error!*Ast.Node { - const token = p.token; + const main_token = p.token; const lhs = try parseIdentifierExpr(p); if (!p.checkToken(.equal)) return parseExprStmt(p, lhs); @@ -733,21 +729,24 @@ fn parseAssignStmt(p: *Parse) Error!*Ast.Node { const rhs = try p.expectExpr(); _ = try p.expectNewline(); - const bytes_start = token.loc.start; - const bytes_end = p.token.loc.start; - return Ast.createBinaryNode(p.arena, .assign_stmt, bytes_start, bytes_end, lhs, rhs); + return .createBinary(p.arena, .assign_stmt, .{ + .start = main_token.loc.start, + .end = p.token.loc.start, + }, lhs, rhs); } fn parseTempDecl(p: *Parse) Error!*Ast.Node { - const token = p.nextToken(); + const main_token = p.nextToken(); const lhs = try p.expectIdentifier(); _ = try p.expectToken(.equal, true); + const rhs = try p.expectExpr(); _ = try p.expectNewline(); - const bytes_start = token.loc.start; - const bytes_end = p.token.loc.start; - return Ast.createBinaryNode(p.arena, .temp_decl, bytes_start, bytes_end, lhs, rhs); + return .createBinary(p.arena, .temp_decl, .{ + .start = main_token.loc.start, + .end = p.token.loc.start, + }, lhs, rhs); } fn parseTildeStmt(p: *Parse) Error!*Ast.Node { @@ -755,7 +754,7 @@ fn parseTildeStmt(p: *Parse) Error!*Ast.Node { defer p.popGrammar(); _ = p.nextToken(); - const node: *Ast.Node = switch (p.token.tag) { + const node = switch (p.token.tag) { .keyword_temp => try parseTempDecl(p), .keyword_return => try parseReturnStmt(p), .identifier => try parseAssignStmt(p), @@ -766,17 +765,17 @@ fn parseTildeStmt(p: *Parse) Error!*Ast.Node { fn parseReturnStmt(p: *Parse) Error!*Ast.Node { var node: ?*Ast.Node = null; - const token = p.nextToken(); + const main_token = p.nextToken(); if (!p.checkToken(.newline) and !p.checkToken(.eof)) { node = try parseInfixExpr(p, null, .none); } + _ = try p.expectNewline(); - - const bytes_start = token.loc.start; - const bytes_end = p.token.loc.start; - - return Ast.createBinaryNode(p.arena, .return_stmt, bytes_start, bytes_end, node, null); + return .createBinary(p.arena, .return_stmt, .{ + .start = main_token.loc.start, + .end = p.token.loc.start, + }, node, null); } fn parseIdentifierExpr(p: *Parse) Error!*Ast.Node { @@ -787,97 +786,44 @@ fn parseIdentifierExpr(p: *Parse) Error!*Ast.Node { .dot => { _ = p.nextToken(); const rhs = try p.expectIdentifier(); - const bytes_start = lhs.source_start; - const bytes_end = p.token.loc.start; - - lhs = try Ast.createBinaryNode( - p.arena, - .selector_expr, - bytes_start, - bytes_end, - lhs, - rhs, - ); + lhs = try Ast.Node.createBinary(p.arena, .selector_expr, .{ + .start = lhs.loc.start, + .end = p.token.loc.start, + }, lhs, rhs); }, .left_paren => { const rhs = try parseArgumentList(p); - const bytes_start = lhs.source_start; - const bytes_end = p.token.loc.start; - - return Ast.createBinaryNode( - p.arena, - .call_expr, - bytes_start, - bytes_end, - lhs, - rhs, - ); + return .createBinary(p.arena, .call_expr, .{ + .start = lhs.loc.start, + .end = p.token.loc.start, + }, lhs, rhs); }, 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) { - 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); - } - - 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 parseDivertExpr(p: *Parse) Error!*Ast.Node { + const main_token = p.nextToken(); + const node = try parseIdentifierExpr(p); + return .createBinary(p.arena, .divert_expr, .{ + .start = main_token.loc.start, + .end = p.token.loc.start, + }, node, null); } -fn parseArgumentList(p: *Parse) Error!?*Ast.Node { - const context = p.makeStmtContext(.block, null); - const token = try p.expectToken(.left_paren, true); +fn parseDivertStmt(p: *Parse) Error!*Ast.Node { + try p.pushGrammar(.expression); + 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 node = try parseContentExpr(p, &token_set); - const end_token = try p.expectNewline(); - const span_start = main_token.loc.start; - const span_end = end_token.loc.start; - return Ast.createBinaryNode(p.arena, .content_stmt, span_start, span_end, node, null); + const node = try parseDivertExpr(p); + _ = try p.expectNewline(); + + return .createBinary(p.arena, .divert_stmt, .{ + .start = main_token.loc.start, + .end = p.token.loc.start, + }, node, null); } fn parseChoiceExpr(p: *Parse) Error!?*Ast.Node { @@ -886,7 +832,7 @@ fn parseChoiceExpr(p: *Parse) Error!?*Ast.Node { .right_bracket, .right_arrow, .newline, .eof, }; - const token = p.token; + const main_token = p.token; var lhs: ?*Ast.Node = null; var mhs: ?*Ast.Node = null; var rhs: ?*Ast.Node = null; @@ -920,50 +866,286 @@ fn parseChoiceExpr(p: *Parse) Error!?*Ast.Node { } } } - - const bytes_start = token.loc.start; - const bytes_end = p.token.loc.start; - return Ast.createChoiceExprNode(p.arena, .choice_expr, bytes_start, bytes_end, lhs, mhs, rhs); + return .createChoice(p.arena, .choice_expr, .{ + .start = main_token.loc.start, + .end = p.token.loc.start, + }, lhs, mhs, rhs); } fn parseChoiceStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node { - const token = p.token; - const level = p.eatTokenLooped(token.tag, true); + const main_token = p.token; + const level = p.eatTokenLooped(main_token.tag, true); const node = try parseChoiceExpr(p); - const bytes_start = token.loc.start; - const bytes_end = if (node) |n| n.source_end else p.token.loc.start; - + const span_end = if (node) |n| n.loc.end else p.token.loc.start; context.level = level; 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 { - const token = p.token; - const level = p.eatTokenLooped(token.tag, true); - const bytes_start = token.loc.start; - const bytes_end = p.token.loc.start; - + const main_token = p.token; + const level = p.eatTokenLooped(main_token.tag, true); + const span_end = p.token.loc.start; 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 { try p.pushGrammar(.expression); defer p.popGrammar(); - const token = p.nextToken(); + const main_token = p.nextToken(); const lhs = try p.expectIdentifier(); _ = try p.expectToken(.equal, true); const rhs = try p.expectExpr(); const last_token = try p.expectNewline(); - const bytes_start = token.loc.start; - const bytes_end = last_token.loc.start; - return Ast.createBinaryNode(p.arena, tag, bytes_start, bytes_end, lhs, rhs); + return .createBinary(p.arena, tag, .{ + .start = main_token.loc.start, + .end = last_token.loc.start, + }, lhs, rhs); } fn parseVarDecl(p: *Parse) Error!*Ast.Node { @@ -976,6 +1158,43 @@ fn parseConstDecl(p: *Parse) Error!*Ast.Node { 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 { p.eatToken(.whitespace); @@ -983,15 +1202,14 @@ fn parseStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node { .star, .plus => try parseChoiceStmt(p, context), .minus => switch (context.tag) { .block => try parseGatherPointStmt(p, context), - else => unreachable, - //.conditional => try parseConditionalStmt(p, context), + .conditional => try parseConditionalStmt(p, context), }, .equal, .equal_equal => switch (context.tag) { - //.block => try parseKnotDecl(p), + .block => try parseKnotDecl(p), else => try parseContentStmt(p), }, .tilde => try parseTildeStmt(p), - //.right_arrow => try parseDivertStmt(p), + .right_arrow => try parseDivertStmt(p), .right_brace => { const token = p.nextToken(); return p.fail(.unexpected_token, token); @@ -1022,12 +1240,11 @@ pub fn parseFile(p: *Parse) Error!*Ast.Node { try p.pushGrammar(.content); defer p.popGrammar(); - const token = p.nextToken(); + const main_token = p.nextToken(); while (!p.checkToken(.eof)) { const node = parseStmt(p, &context) catch |err| switch (err) { error.ParseError => { if (p.panic_mode) p.synchronize(); - p.eatToken(.newline); continue; }, @@ -1039,7 +1256,8 @@ pub fn parseFile(p: *Parse) Error!*Ast.Node { const node = try p.collectKnot(&context); if (node) |n| try p.scratch.append(p.gpa, n); - const span_start = token.loc.start; - const span_end = p.token.loc.end; - return p.makeNodeSequence(&context, .file, span_start, span_end, context.scratch_top); + return p.makeNodeSequence(&context, .file, .{ + .start = main_token.loc.start, + .end = p.token.loc.end, + }, context.scratch_top); } diff --git a/src/main.zig b/src/main.zig index be88392..f4f56ed 100644 --- a/src/main.zig +++ b/src/main.zig @@ -31,11 +31,25 @@ fn mainArgs( ) !void { var source_path: ?[]const u8 = null; 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) { const arg = args_list[arg_index]; 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) { source_path = arg; } else { @@ -62,7 +76,8 @@ fn mainArgs( var stdout_writer = stdout.writer(&stdout_buffer); 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(); } diff --git a/src/root.zig b/src/root.zig index 1964097..cc553b6 100644 --- a/src/root.zig +++ b/src/root.zig @@ -4,8 +4,8 @@ pub const Ast = @import("Ast.zig"); pub const Story = struct { pub const LoadOptions = struct { - stream_writer: *std.Io.Writer, - use_color: bool = true, + dump_writer: *std.Io.Writer, + dump_use_color: bool = true, }; pub fn loadFromString( @@ -20,12 +20,12 @@ pub const Story = struct { var ast = try Ast.parse(gpa, arena, source_bytes, "", 0); defer ast.deinit(gpa); - try ast.render(gpa, options.stream_writer, .{ - .use_color = options.use_color, + try ast.render(gpa, options.dump_writer, .{ + .use_color = options.dump_use_color, }); if (ast.errors.len > 0) { - try ast.renderErrors(gpa, options.stream_writer, .{ - .use_color = options.use_color, + try ast.renderErrors(gpa, options.dump_writer, .{ + .use_color = options.dump_use_color, }); return error.Invalid; } diff --git a/testing/regression/CMakeLists.txt b/testing/regression/CMakeLists.txt new file mode 100644 index 0000000..4e61a2a --- /dev/null +++ b/testing/regression/CMakeLists.txt @@ -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}" +) diff --git a/testing/regression/lit.cfg.py b/testing/regression/lit.cfg.py new file mode 100644 index 0000000..6cdd27b --- /dev/null +++ b/testing/regression/lit.cfg.py @@ -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)) diff --git a/testing/regression/lit.site.cfg.py.in b/testing/regression/lit.site.cfg.py.in new file mode 100644 index 0000000..8765729 --- /dev/null +++ b/testing/regression/lit.site.cfg.py.in @@ -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")) diff --git a/testing/regression/syntax/choice-branch-mixed.ink b/testing/regression/syntax/choice-branch-mixed.ink new file mode 100644 index 0000000..ece24ca --- /dev/null +++ b/testing/regression/syntax/choice-branch-mixed.ink @@ -0,0 +1,11 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ChoiceStmt +// CHECK-NEXT: `--ChoiceStarStmt +// CHECK-NEXT: `--ChoiceContentExpr +// CHECK-NEXT: |--ChoiceStartContentExpr `A` +// CHECK-NEXT: `--ChoiceInnerContentExpr `B` + +* A[]B diff --git a/testing/regression/syntax/choice-nesting-normalize.ink b/testing/regression/syntax/choice-nesting-normalize.ink new file mode 100644 index 0000000..384f461 --- /dev/null +++ b/testing/regression/syntax/choice-nesting-normalize.ink @@ -0,0 +1,26 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ChoiceStmt +// CHECK-NEXT: |--ChoiceStarStmt +// CHECK-NEXT: | `--ChoiceContentExpr +// CHECK-NEXT: | `--ChoiceStartContentExpr `A` +// CHECK-NEXT: |--ChoiceStarStmt +// CHECK-NEXT: | `--ChoiceContentExpr +// CHECK-NEXT: | `--ChoiceStartContentExpr `B` +// CHECK-NEXT: |--ChoiceStarStmt +// CHECK-NEXT: | `--ChoiceContentExpr +// CHECK-NEXT: | `--ChoiceStartContentExpr `C` +// CHECK-NEXT: |--ChoiceStarStmt +// CHECK-NEXT: | `--ChoiceContentExpr +// CHECK-NEXT: | `--ChoiceStartContentExpr `D` +// CHECK-NEXT: `--ChoiceStarStmt +// CHECK-NEXT: `--ChoiceContentExpr +// CHECK-NEXT: `--ChoiceStartContentExpr `E` + +***** A +**** B +*** C +** D +* E diff --git a/testing/regression/syntax/conditional-logical-and.ink b/testing/regression/syntax/conditional-logical-and.ink new file mode 100644 index 0000000..6ab45b0 --- /dev/null +++ b/testing/regression/syntax/conditional-logical-and.ink @@ -0,0 +1,27 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: |--TempDecl +// CHECK-NEXT: | |--Identifier `x` +// CHECK-NEXT: | `--NumberLiteral `2` +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--IfStmt +// CHECK-NEXT: |--LogicalAndExpr +// CHECK-NEXT: | |--LogicalGreaterThanOrEqualExpr +// CHECK-NEXT: | | |--Identifier `x` +// CHECK-NEXT: | | `--NumberLiteral `1` +// CHECK-NEXT: | `--LogicalLesserThanOrEqualExpr +// CHECK-NEXT: | |--Identifier `x` +// CHECK-NEXT: | `--NumberLiteral `10` +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `Between one and ten!` + +~ temp x = 2 + +{x >= 1 and x <= 10: + Between one and ten! +} diff --git a/testing/regression/syntax/conditional-logical-or.ink b/testing/regression/syntax/conditional-logical-or.ink new file mode 100644 index 0000000..3e05548 --- /dev/null +++ b/testing/regression/syntax/conditional-logical-or.ink @@ -0,0 +1,27 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: |--TempDecl +// CHECK-NEXT: | |--Identifier `x` +// CHECK-NEXT: | `--NumberLiteral `2` +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--IfStmt +// CHECK-NEXT: |--LogicalOrExpr +// CHECK-NEXT: | |--LogicalGreaterThanOrEqualExpr +// CHECK-NEXT: | | |--Identifier `x` +// CHECK-NEXT: | | `--NumberLiteral `1` +// CHECK-NEXT: | `--LogicalEqualityExpr +// CHECK-NEXT: | |--Identifier `x` +// CHECK-NEXT: | `--FalseLiteral +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `Greater than one or false!` + +~ temp x = 2 + +{x >= 1 or x == false: + Greater than one or false! +} diff --git a/testing/regression/syntax/conditional-simple-if-else.ink b/testing/regression/syntax/conditional-simple-if-else.ink new file mode 100644 index 0000000..d355eaf --- /dev/null +++ b/testing/regression/syntax/conditional-simple-if-else.ink @@ -0,0 +1,23 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--IfStmt +// CHECK-NEXT: |--TrueLiteral +// CHECK-NEXT: |--BlockStmt +// CHECK-NEXT: | `--ContentStmt +// CHECK-NEXT: | `--Content +// CHECK-NEXT: | `--StringLiteral `Hello, world!` +// CHECK-NEXT: `--ElseBranch +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `Unreachable!` + +{true: + Hello, world! +- else: + Unreachable! +} diff --git a/testing/regression/syntax/conditional-simple-if.ink b/testing/regression/syntax/conditional-simple-if.ink new file mode 100644 index 0000000..760acfd --- /dev/null +++ b/testing/regression/syntax/conditional-simple-if.ink @@ -0,0 +1,16 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--IfStmt +// CHECK-NEXT: |--TrueLiteral +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `Hello, world!` + +{true: + Hello, world! +} diff --git a/testing/regression/syntax/conditional-simple-switch.ink b/testing/regression/syntax/conditional-simple-switch.ink new file mode 100644 index 0000000..2bfcaa8 --- /dev/null +++ b/testing/regression/syntax/conditional-simple-switch.ink @@ -0,0 +1,43 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: |--TempDecl +// CHECK-NEXT: | |--Identifier `x` +// CHECK-NEXT: | `--NumberLiteral `3` +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--SwitchStmt +// CHECK-NEXT: |--Identifier `x` +// CHECK-NEXT: |--SwitchCase +// CHECK-NEXT: | |--NumberLiteral `0` +// CHECK-NEXT: | `--BlockStmt +// CHECK-NEXT: | `--ContentStmt +// CHECK-NEXT: | `--Content +// CHECK-NEXT: | `--StringLiteral `zero` +// CHECK-NEXT: |--SwitchCase +// CHECK-NEXT: | |--NumberLiteral `1` +// CHECK-NEXT: | `--BlockStmt +// CHECK-NEXT: | `--ContentStmt +// CHECK-NEXT: | `--Content +// CHECK-NEXT: | `--StringLiteral `one` +// CHECK-NEXT: |--SwitchCase +// CHECK-NEXT: | |--NumberLiteral `2` +// CHECK-NEXT: | `--BlockStmt +// CHECK-NEXT: | `--ContentStmt +// CHECK-NEXT: | `--Content +// CHECK-NEXT: | `--StringLiteral `two` +// CHECK-NEXT: `--ElseBranch +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `lots` + +~ temp x = 3 + +{ x: +- 0: zero +- 1: one +- 2: two +- else: lots +} diff --git a/testing/regression/syntax/conditional-substitution.ink b/testing/regression/syntax/conditional-substitution.ink new file mode 100644 index 0000000..68e9c2f --- /dev/null +++ b/testing/regression/syntax/conditional-substitution.ink @@ -0,0 +1 @@ +{true: Hello world!} diff --git a/testing/regression/syntax/const-expected-expression.ink b/testing/regression/syntax/const-expected-expression.ink new file mode 100644 index 0000000..03f8805 --- /dev/null +++ b/testing/regression/syntax/const-expected-expression.ink @@ -0,0 +1 @@ +CONST foo = diff --git a/testing/regression/syntax/content-substitution.ink b/testing/regression/syntax/content-substitution.ink new file mode 100644 index 0000000..2dc97a6 --- /dev/null +++ b/testing/regression/syntax/content-substitution.ink @@ -0,0 +1,23 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: |--VarDecl +// CHECK-NEXT: | |--Identifier `x` +// CHECK-NEXT: | `--StringExpr `"Hello world 1"` +// CHECK-NEXT: | `--StringLiteral `Hello world 1` +// CHECK-NEXT: |--ContentStmt +// CHECK-NEXT: | `--Content +// CHECK-NEXT: | `--InlineLogicExpr +// CHECK-NEXT: | `--Identifier `x` +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: |--StringLiteral `Hello ` +// CHECK-NEXT: |--InlineLogicExpr +// CHECK-NEXT: | `--StringExpr `"world"` +// CHECK-NEXT: | `--StringLiteral `world` +// CHECK-NEXT: `--StringLiteral ` 2.` + +VAR x = "Hello world 1" +{x} +Hello {"world"} 2. diff --git a/testing/regression/syntax/empty-choice-branch.ink b/testing/regression/syntax/empty-choice-branch.ink new file mode 100644 index 0000000..1b98e2f --- /dev/null +++ b/testing/regression/syntax/empty-choice-branch.ink @@ -0,0 +1,9 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ChoiceStmt +// CHECK-NEXT: `--ChoiceStarStmt +// CHECK-NEXT: `--ChoiceContentExpr + +* diff --git a/testing/regression/syntax/empty-file.ink b/testing/regression/syntax/empty-file.ink new file mode 100644 index 0000000..abb3c19 --- /dev/null +++ b/testing/regression/syntax/empty-file.ink @@ -0,0 +1,3 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" diff --git a/testing/regression/syntax/empty-knots.ink b/testing/regression/syntax/empty-knots.ink new file mode 100644 index 0000000..dffcaf6 --- /dev/null +++ b/testing/regression/syntax/empty-knots.ink @@ -0,0 +1,12 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: |--KnotDecl +// CHECK-NEXT: | `--KnotProto +// CHECK-NEXT: | `--Identifier `a` +// CHECK-NEXT: `--KnotDecl +// CHECK-NEXT: `--KnotProto +// CHECK-NEXT: `--Identifier `b` + +== a +== b diff --git a/testing/regression/syntax/empty-logic.ink b/testing/regression/syntax/empty-logic.ink new file mode 100644 index 0000000..54bcf30 --- /dev/null +++ b/testing/regression/syntax/empty-logic.ink @@ -0,0 +1 @@ +~ diff --git a/testing/regression/syntax/expression-statement.ink b/testing/regression/syntax/expression-statement.ink new file mode 100644 index 0000000..86c2e82 --- /dev/null +++ b/testing/regression/syntax/expression-statement.ink @@ -0,0 +1,16 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ExprStmt +// CHECK-NEXT: `--SubtractExpr +// CHECK-NEXT: |--MultiplyExpr +// CHECK-NEXT: | |--AddExpr +// CHECK-NEXT: | | |--NegateExpr +// CHECK-NEXT: | | | `--NumberLiteral `1` +// CHECK-NEXT: | | `--NumberLiteral `2` +// CHECK-NEXT: | `--NumberLiteral `3` +// CHECK-NEXT: `--NegateExpr +// CHECK-NEXT: `--NumberLiteral `4` + +~ (-1 + 2) * 3 - -4 diff --git a/testing/regression/syntax/factorial.ink b/testing/regression/syntax/factorial.ink new file mode 100644 index 0000000..9db358d --- /dev/null +++ b/testing/regression/syntax/factorial.ink @@ -0,0 +1,46 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: |--BlockStmt +// CHECK-NEXT: | `--ContentStmt +// CHECK-NEXT: | `--Content +// CHECK-NEXT: | `--InlineLogicExpr +// CHECK-NEXT: | `--CallExpr +// CHECK-NEXT: | |--Identifier `factorial` +// CHECK-NEXT: | `--ArgumentList +// CHECK-NEXT: | `--NumberLiteral `10` +// CHECK-NEXT: `--FunctionDecl +// CHECK-NEXT: |--FunctionProto +// CHECK-NEXT: | |--Identifier `factorial` +// CHECK-NEXT: | `--ParamList +// CHECK-NEXT: | `--ParamDecl `n` +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--IfStmt +// CHECK-NEXT: |--LogicalEqualityExpr +// CHECK-NEXT: | |--Identifier `n` +// CHECK-NEXT: | `--NumberLiteral `1` +// CHECK-NEXT: |--BlockStmt +// CHECK-NEXT: | `--ReturnStmt +// CHECK-NEXT: | `--NumberLiteral `1` +// CHECK-NEXT: `--ElseBranch +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ReturnStmt +// CHECK-NEXT: `--MultiplyExpr +// CHECK-NEXT: |--Identifier `n` +// CHECK-NEXT: `--CallExpr +// CHECK-NEXT: |--Identifier `factorial` +// CHECK-NEXT: `--ArgumentList +// CHECK-NEXT: `--SubtractExpr +// CHECK-NEXT: |--Identifier `n` +// CHECK-NEXT: `--NumberLiteral `1` + +{ factorial(10) } + +== function factorial(n) + { n == 1: + ~ return 1 + - else: + ~ return (n * factorial(n - 1)) + } diff --git a/testing/regression/syntax/fibonacci.ink b/testing/regression/syntax/fibonacci.ink new file mode 100644 index 0000000..0acbe9a --- /dev/null +++ b/testing/regression/syntax/fibonacci.ink @@ -0,0 +1,62 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: |--BlockStmt +// CHECK-NEXT: | `--ContentStmt +// CHECK-NEXT: | `--Content +// CHECK-NEXT: | `--InlineLogicExpr +// CHECK-NEXT: | `--CallExpr +// CHECK-NEXT: | |--Identifier `fib` +// CHECK-NEXT: | `--ArgumentList +// CHECK-NEXT: | `--NumberLiteral `10` +// CHECK-NEXT: `--FunctionDecl +// CHECK-NEXT: |--FunctionProto +// CHECK-NEXT: | |--Identifier `fib` +// CHECK-NEXT: | `--ParamList +// CHECK-NEXT: | `--ParamDecl `n` +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--MultiIfStmt +// CHECK-NEXT: |--IfBranch +// CHECK-NEXT: | |--LogicalEqualityExpr +// CHECK-NEXT: | | |--Identifier `n` +// CHECK-NEXT: | | `--NumberLiteral `0` +// CHECK-NEXT: | `--BlockStmt +// CHECK-NEXT: | `--ReturnStmt +// CHECK-NEXT: | `--NumberLiteral `0` +// CHECK-NEXT: |--IfBranch +// CHECK-NEXT: | |--LogicalEqualityExpr +// CHECK-NEXT: | | |--Identifier `n` +// CHECK-NEXT: | | `--NumberLiteral `1` +// CHECK-NEXT: | `--BlockStmt +// CHECK-NEXT: | `--ReturnStmt +// CHECK-NEXT: | `--NumberLiteral `1` +// CHECK-NEXT: `--ElseBranch +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ReturnStmt +// CHECK-NEXT: `--AddExpr +// CHECK-NEXT: |--CallExpr +// CHECK-NEXT: | |--Identifier `fib` +// CHECK-NEXT: | `--ArgumentList +// CHECK-NEXT: | `--SubtractExpr +// CHECK-NEXT: | |--Identifier `n` +// CHECK-NEXT: | `--NumberLiteral `1` +// CHECK-NEXT: `--CallExpr +// CHECK-NEXT: |--Identifier `fib` +// CHECK-NEXT: `--ArgumentList +// CHECK-NEXT: `--SubtractExpr +// CHECK-NEXT: |--Identifier `n` +// CHECK-NEXT: `--NumberLiteral `2` + +{ fib(10) } + +== function fib(n) + { + - n == 0: + ~ return 0 + - n == 1: + ~ return 1 + - else: + ~ return fib(n - 1) + fib(n - 2) + } diff --git a/testing/regression/syntax/function-simple.ink b/testing/regression/syntax/function-simple.ink new file mode 100644 index 0000000..7464678 --- /dev/null +++ b/testing/regression/syntax/function-simple.ink @@ -0,0 +1,28 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: |--BlockStmt +// CHECK-NEXT: | `--ContentStmt +// CHECK-NEXT: | `--Content +// CHECK-NEXT: | `--InlineLogicExpr +// CHECK-NEXT: | `--CallExpr +// CHECK-NEXT: | |--Identifier `func` +// CHECK-NEXT: | `--ArgumentList +// CHECK-NEXT: | |--NumberLiteral `1` +// CHECK-NEXT: | `--NumberLiteral `2` +// CHECK-NEXT: `--FunctionDecl +// CHECK-NEXT: |--FunctionProto +// CHECK-NEXT: | |--Identifier `func` +// CHECK-NEXT: | `--ParamList +// CHECK-NEXT: | |--ParamDecl `a` +// CHECK-NEXT: | `--ParamDecl `b` +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ReturnStmt +// CHECK-NEXT: `--AddExpr +// CHECK-NEXT: |--Identifier `a` +// CHECK-NEXT: `--Identifier `b` + +{func(1, 2)} + +== function func(a, b) +~ return a + b diff --git a/testing/regression/syntax/hello-world.ink b/testing/regression/syntax/hello-world.ink new file mode 100644 index 0000000..259d29d --- /dev/null +++ b/testing/regression/syntax/hello-world.ink @@ -0,0 +1,9 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `Hello, world!` + +Hello, world! diff --git a/testing/regression/syntax/knot-expected-identifier.ink b/testing/regression/syntax/knot-expected-identifier.ink new file mode 100644 index 0000000..7ebb478 --- /dev/null +++ b/testing/regression/syntax/knot-expected-identifier.ink @@ -0,0 +1,2 @@ +=== +Hello, world! diff --git a/testing/regression/syntax/knot-qualified-lookup.ink b/testing/regression/syntax/knot-qualified-lookup.ink new file mode 100644 index 0000000..2f3b86d --- /dev/null +++ b/testing/regression/syntax/knot-qualified-lookup.ink @@ -0,0 +1,34 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: |--BlockStmt +// CHECK-NEXT: | `--DivertStmt +// CHECK-NEXT: | `--Divert +// CHECK-NEXT: | `--CallExpr +// CHECK-NEXT: | |--SelectorExpr +// CHECK-NEXT: | | |--Identifier `a` +// CHECK-NEXT: | | `--Identifier `b` +// CHECK-NEXT: | `--ArgumentList +// CHECK-NEXT: | `--StringExpr `"Brett"` +// CHECK-NEXT: | `--StringLiteral `Brett` +// CHECK-NEXT: `--KnotDecl +// CHECK-NEXT: |--KnotProto +// CHECK-NEXT: | `--Identifier `a` +// CHECK-NEXT: `--StitchDecl +// CHECK-NEXT: |--StitchProto +// CHECK-NEXT: | |--Identifier `b` +// CHECK-NEXT: | `--ParamList +// CHECK-NEXT: | `--ParamDecl `name` +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: |--StringLiteral `Hello, ` +// CHECK-NEXT: |--InlineLogicExpr +// CHECK-NEXT: | `--Identifier `name` +// CHECK-NEXT: `--StringLiteral `!` + +-> a.b("Brett") + +== a += b(name) +Hello, {name}! diff --git a/testing/regression/syntax/knot-stitches.ink b/testing/regression/syntax/knot-stitches.ink new file mode 100644 index 0000000..8caa5be --- /dev/null +++ b/testing/regression/syntax/knot-stitches.ink @@ -0,0 +1,31 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--KnotDecl +// CHECK-NEXT: |--KnotProto +// CHECK-NEXT: | `--Identifier `knot` +// CHECK-NEXT: |--BlockStmt +// CHECK-NEXT: | `--ContentStmt +// CHECK-NEXT: | `--Content +// CHECK-NEXT: | `--StringLiteral `Hello from `knot`!` +// CHECK-NEXT: |--StitchDecl +// CHECK-NEXT: | |--StitchProto +// CHECK-NEXT: | | `--Identifier `a` +// CHECK-NEXT: | `--BlockStmt +// CHECK-NEXT: | `--ContentStmt +// CHECK-NEXT: | `--Content +// CHECK-NEXT: | `--StringLiteral `Hello from `knot.a`!` +// CHECK-NEXT: `--StitchDecl +// CHECK-NEXT: |--StitchProto +// CHECK-NEXT: | `--Identifier `b` +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `Hello from `knot.b`!` + +=== knot === +Hello from `knot`! += a +Hello from `knot.a`! += b +Hello from `knot.b`! diff --git a/testing/regression/syntax/nested-stitch.ink b/testing/regression/syntax/nested-stitch.ink new file mode 100644 index 0000000..d57f3ec --- /dev/null +++ b/testing/regression/syntax/nested-stitch.ink @@ -0,0 +1,17 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--KnotDecl +// CHECK-NEXT: |--KnotProto +// CHECK-NEXT: | `--Identifier `knot` +// CHECK-NEXT: `--StitchDecl +// CHECK-NEXT: |--StitchProto +// CHECK-NEXT: | `--Identifier `stitch` +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `Hello, world!` + +=== knot === += stitch +Hello, world! diff --git a/testing/regression/syntax/simple-assignment.ink b/testing/regression/syntax/simple-assignment.ink new file mode 100644 index 0000000..c7a35cf --- /dev/null +++ b/testing/regression/syntax/simple-assignment.ink @@ -0,0 +1,13 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: |--TempDecl +// CHECK-NEXT: | |--Identifier `a` +// CHECK-NEXT: | `--NumberLiteral `123` +// CHECK-NEXT: `--AssignStmt +// CHECK-NEXT: |--Identifier `a` +// CHECK-NEXT: `--NumberLiteral `321` + +~ temp a = 123 +~ a = 321 diff --git a/testing/regression/syntax/simple-choice.ink b/testing/regression/syntax/simple-choice.ink new file mode 100644 index 0000000..25cd253 --- /dev/null +++ b/testing/regression/syntax/simple-choice.ink @@ -0,0 +1,18 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ChoiceStmt +// CHECK-NEXT: |--ChoiceStarStmt +// CHECK-NEXT: | `--ChoiceContentExpr +// CHECK-NEXT: | `--ChoiceStartContentExpr `A` +// CHECK-NEXT: |--ChoiceStarStmt +// CHECK-NEXT: | `--ChoiceContentExpr +// CHECK-NEXT: | `--ChoiceStartContentExpr `B` +// CHECK-NEXT: `--ChoiceStarStmt +// CHECK-NEXT: `--ChoiceContentExpr +// CHECK-NEXT: `--ChoiceStartContentExpr `C` + +* A +* B +* C diff --git a/testing/regression/syntax/simple-knot.ink b/testing/regression/syntax/simple-knot.ink new file mode 100644 index 0000000..a08acb0 --- /dev/null +++ b/testing/regression/syntax/simple-knot.ink @@ -0,0 +1,13 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--KnotDecl +// CHECK-NEXT: |--KnotProto +// CHECK-NEXT: | `--Identifier `knot` +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `Hello, world!` + +=== knot === +Hello, world! diff --git a/testing/regression/syntax/simple-stitch.ink b/testing/regression/syntax/simple-stitch.ink new file mode 100644 index 0000000..099844f --- /dev/null +++ b/testing/regression/syntax/simple-stitch.ink @@ -0,0 +1,13 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--StitchDecl +// CHECK-NEXT: |--StitchProto +// CHECK-NEXT: | `--Identifier `stitch` +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `Hello, world!` + += stitch +Hello, world! diff --git a/testing/regression/syntax/var-expected-expression.ink b/testing/regression/syntax/var-expected-expression.ink new file mode 100644 index 0000000..e7afc0c --- /dev/null +++ b/testing/regression/syntax/var-expected-expression.ink @@ -0,0 +1 @@ +VAR foo =