Add switch statements

This commit is contained in:
jaina heartles 2022-12-10 01:21:39 -08:00
parent b2007131c8
commit ad513d70e2
2 changed files with 199 additions and 7 deletions

View file

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
pub fn main() !void { pub fn main() !void {
const Enum = enum { foo, bar }; const Enum = enum { foo, bar, baz };
try execute( try execute(
std.io.getStdOut().writer(), std.io.getStdOut().writer(),
.{ .test_tmpl = "{.x} {%context_foo}" }, .{ .test_tmpl = "{.x} {%context_foo}" },
@ -20,6 +20,11 @@ pub fn main() !void {
.maybe_foo = @as(?[]const u8, "foo"), .maybe_foo = @as(?[]const u8, "foo"),
.maybe_bar = @as(?[]const u8, null), .maybe_bar = @as(?[]const u8, null),
.snap = Enum.bar, .snap = Enum.bar,
.crackle = union(Enum) {
foo: []const u8,
bar: []const u8,
baz: []const u8,
}{ .foo = "abcd" },
.x = "y", .x = "y",
}, },
.{ .{
@ -122,6 +127,44 @@ fn executeStatement(
}; };
} }
}, },
.@"switch" => |switch_stmt| {
const expr = evaluateExpression(switch_stmt.expression, args, captures, context);
var found = false;
inline for (switch_stmt.cases) |case| {
if (std.meta.isTag(expr, case.header.tag)) {
found = true;
if (case.header.capture) |capture| {
try executeTemplate(
writer,
templates,
case.subtemplate,
args,
addCapture(captures, capture, @field(expr, case.header.tag)),
context,
);
} else {
try executeTemplate(
writer,
templates,
case.subtemplate,
args,
captures,
context,
);
}
}
} else if (!found) if (switch_stmt.else_branch) |subtemplate| {
try executeTemplate(
writer,
templates,
subtemplate,
args,
captures,
context,
);
};
},
.call_template => |call| { .call_template => |call| {
const new_template = @field(templates, call.template_name); const new_template = @field(templates, call.template_name);
try execute( try execute(
@ -237,6 +280,7 @@ const TemplateType = enum {
for_block, for_block,
if_block, if_block,
if_else_block, if_else_block,
switch_block,
}; };
const TemplateParseResult = struct { const TemplateParseResult = struct {
@ -293,6 +337,7 @@ fn parseTemplate(
comptime template_type: TemplateType, comptime template_type: TemplateType,
) TemplateParseResult { ) TemplateParseResult {
comptime { comptime {
@setEvalBranchQuota(tokens.len * 100);
var i: usize = start; var i: usize = start;
var current_text: []const u8 = ""; var current_text: []const u8 = "";
var items: []const TemplateItem = &.{}; var items: []const TemplateItem = &.{};
@ -336,6 +381,41 @@ fn parseTemplate(
}}; }};
i = subtemplate.new_idx; i = subtemplate.new_idx;
}, },
.switch_header => |header| {
var cases: []const Case = &.{};
var else_branch: ?[]const TemplateItem = null;
var last_header: CaseHeader = header.first_case;
var is_else = false;
while (true) {
const case = parseTemplate(tokens, i + 1, .switch_block);
i = case.new_idx;
if (!is_else) {
cases = cases ++ [_]Case{.{
.header = last_header,
.subtemplate = case.items,
}};
} else {
else_branch = case.items;
}
switch (case.closing_block.?.block) {
.end_switch => break,
.@"else" => is_else = true,
.case_header => |case_header| last_header = case_header,
else => @compileError("Unexpected token"),
}
}
items = items ++ [_]TemplateItem{.{
.statement = .{
.@"switch" = .{
.expression = header.expression,
.cases = cases,
.else_branch = else_branch,
},
},
}};
},
.end_for => if (template_type == .for_block) .end_for => if (template_type == .for_block)
break cb break cb
else else
@ -348,13 +428,17 @@ fn parseTemplate(
break cb break cb
else else
@compileError("Unexpected #elif tag"), @compileError("Unexpected #elif tag"),
.@"else" => if (template_type == .if_block) .@"else" => if (template_type == .if_block or template_type == .switch_block)
break cb break cb
else else
@compileError("Unexpected #else tag"), @compileError("Unexpected #else tag"),
.call_template => |call| items = items ++ [_]TemplateItem{.{ .call_template => |call| items = items ++ [_]TemplateItem{.{
.statement = .{ .call_template = call }, .statement = .{ .call_template = call },
}}, }},
.end_switch, .case_header => if (template_type == .switch_block)
break cb
else
@compileError("Unexpected /switch tag"),
} }
}, },
} }
@ -575,6 +659,16 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken
iter = result.new_iter; iter = result.new_iter;
break .{ .call_template = result.item }; break .{ .call_template = result.item };
}, },
.@"switch" => {
const result = parseSwitchHeader(iter);
iter = result.new_iter;
break .{ .switch_header = result.item };
},
.case => {
const result = parseCaseHeader(iter);
iter = result.new_iter;
break .{ .case_header = result.item };
},
//else => @compileError("TODO"), //else => @compileError("TODO"),
} }
@ -588,6 +682,7 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken
switch (keyword) { switch (keyword) {
.@"for" => break .{ .end_for = {} }, .@"for" => break .{ .end_for = {} },
.@"if" => break .{ .end_if = {} }, .@"if" => break .{ .end_if = {} },
.@"switch" => break .{ .end_switch = {} },
} }
}, },
.period, .dollar, .percent => { .period, .dollar, .percent => {
@ -746,6 +841,57 @@ fn parseIfHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter
} }
} }
fn parseCaseHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, CaseHeader) {
comptime {
var iter = skipWhitespace(tokens);
const tag = iter.next();
expectToken(tag, .text);
const captures = tryParseCapture(iter);
if (captures) |cap| {
if (cap.item.len == 1) {
return .{
.new_iter = cap.new_iter,
.item = CaseHeader{
.tag = tag.?.text,
.capture = cap.item[0],
},
};
} else @compileError("Only one capture allowed for case statements");
}
return .{
.new_iter = iter,
.item = .{
.tag = tag.?.text,
.capture = null,
},
};
}
}
fn parseSwitchHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, SwitchHeader) {
comptime {
const condition = parseExpression(tokens);
var iter = skipWhitespace(condition.new_iter);
const next = iter.next();
expectToken(next, .text);
if (!std.mem.eql(u8, next.?.text, "case")) @compileError("Expected case following switch condition");
iter = skipWhitespace(iter);
const first = parseCaseHeader(iter);
return .{
.new_iter = first.new_iter,
.item = .{
.expression = condition.item,
.first_case = first.item,
},
};
}
}
fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, []const []const u8) { fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, []const []const u8) {
comptime { comptime {
var iter = tokens; var iter = tokens;
@ -755,7 +901,7 @@ fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, [
switch (token) { switch (token) {
.whitespace => {}, .whitespace => {},
.text => |text| { .text => |text| {
if (wants != .text) @compileError("Unexpected token \"" ++ text ++ "\""); if (wants == .period) break;
fields = fields ++ [1][]const u8{text}; fields = fields ++ [1][]const u8{text};
wants = .period; wants = .period;
}, },
@ -763,13 +909,15 @@ fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, [
if (wants != .period) @compileError("Unexpected token \".\""); if (wants != .period) @compileError("Unexpected token \".\"");
wants = .text; wants = .text;
}, },
else => if (wants == .period or fields.len == 0) return .{ else => if (wants == .period or fields.len == 0) break else @compileError("Unexpected token"),
.new_iter = iter,
.item = fields,
} else @compileError("Unexpected token"),
} }
_ = iter.next(); _ = iter.next();
} }
return .{
.new_iter = iter,
.item = fields,
};
} }
} }
@ -838,6 +986,27 @@ const If = struct {
}, },
}; };
const Case = struct {
header: CaseHeader,
subtemplate: []const TemplateItem,
};
const SwitchHeader = struct {
expression: Expression,
first_case: CaseHeader,
};
const CaseHeader = struct {
tag: []const u8,
capture: ?[]const u8,
};
const Switch = struct {
expression: Expression,
cases: []const Case,
else_branch: ?[]const TemplateItem,
};
const CallTemplate = struct { const CallTemplate = struct {
template_name: []const u8, template_name: []const u8,
args: Expression, args: Expression,
@ -852,6 +1021,7 @@ const Statement = union(enum) {
expression: Expression, expression: Expression,
@"for": For, @"for": For,
@"if": If, @"if": If,
@"switch": Switch,
call_template: CallTemplate, call_template: CallTemplate,
}; };
@ -865,6 +1035,9 @@ const ControlBlock = struct {
@"else": void, @"else": void,
elif_header: IfHeader, elif_header: IfHeader,
call_template: CallTemplate, call_template: CallTemplate,
switch_header: SwitchHeader,
case_header: CaseHeader,
end_switch: void,
}; };
block: Data, block: Data,
strip_before: bool, strip_before: bool,
@ -877,11 +1050,14 @@ const Keyword = enum {
@"else", @"else",
@"elif", @"elif",
@"template", @"template",
@"switch",
@"case",
}; };
const EndKeyword = enum { const EndKeyword = enum {
@"for", @"for",
@"if", @"if",
@"switch",
}; };
const Builtin = enum { const Builtin = enum {

View file

@ -26,6 +26,22 @@
neither neither
{=/if} {=/if}
{#switch .snap case foo =}
foo
{= #case bar =}
bar
{= #else =}
other
{= /switch}
crackle: {#switch .crackle case foo |$foo|=}
foo:{$foo}
{= #case bar |$bar|=}
bar:{$bar}
{= #else =}
other
{= /switch}
{#if .maybe_foo |$v|}{$v}{#else}null{/if} {#if .maybe_foo |$v|}{$v}{#else}null{/if}
{#if .maybe_bar |$v|}{$v}{#else}null{/if} {#if .maybe_bar |$v|}{$v}{#else}null{/if}
{#if .maybe_foo |$_|}abcd{#else}null{/if} {#if .maybe_foo |$_|}abcd{#else}null{/if}