const std = @import( "std" ); const Rule = struct { name :[]const u8, options :[]const []const Sequence, pub fn format( x :@This(), writer :anytype ) !void { // try writer.print( "Rule {{ .name = {s}, .options = {} }}\n", .{ x.name, x.options } ); try writer.print( "Rule {{ .name = \"{s}\", .options = .{{\n", .{ x.name } ); for( x.options ) |sequences| { try writer.print( "\t[{d}]Sequence {{\n", .{ sequences.len } ); for( sequences ) |sequence| { try writer.print( "\t\t{f},\n", .{ sequence } ); } try writer.print( "\t}}\n", .{} ); } try writer.print( "}} }}\n", .{} ); } }; const Sequence = struct { term :Term, suffix :Suffix, pub fn format( x :@This(), writer :anytype ) !void { try writer.print( "Sequence {{ .term = {f}, .suffix = {f} }}", .{ x.term, x.suffix } ); } }; const Option = []const Sequence; const Term = union( enum ) { token :[]const u8, rule :[]const u8, pub fn format( x :@This(), writer :anytype ) !void { const str = switch( x ) { .token => |token_str| token_str, .rule => | rule_str| rule_str, }; try writer.print( "Term {{ .{s} = \"{s}\" }}", .{ @tagName( x ), str } ); } }; const Suffix = enum( u8 ) { zero_or_one = '?', one_or_many = '+', zero_or_many = '*', one = '1', pub fn format( x :@This(), writer :anytype ) !void { try writer.print( "Suffix {{ '{c}' }}", .{ @intFromEnum( x ) } ); } }; const grammar = [_]Rule { Rule { .name = "grammar", .options = &[_][]const Sequence { &[_]Sequence { Sequence { .term = .{ .token = "_" }, .suffix = .one }, Sequence { .term = .{ .rule = "rule" }, .suffix = .zero_or_many }, }, } }, Rule { .name = "rule", .options = &[_][]const Sequence { &[_]Sequence { Sequence { .term = .{ .token = "identifier" }, .suffix = .one }, Sequence { .term = .{ .token = "_" }, .suffix = .one }, Sequence { .term = .{ .token = "=" }, .suffix = .one }, Sequence { .term = .{ .token = "_" }, .suffix = .one }, Sequence { .term = .{ .rule = "sequence" }, .suffix = .zero_or_many }, Sequence { .term = .{ .token = ";" }, .suffix = .one }, Sequence { .term = .{ .token = "_" }, .suffix = .one }, } } }, Rule { .name = "sequence", .options = &[_][]const Sequence { &[_]Sequence { Sequence { .term = .{ .rule = "term" }, .suffix = .one }, Sequence { .term = .{ .rule = "suffix" }, .suffix = .one }, } } }, Rule { .name = "term", .options = &[_][]const Sequence { &[_]Sequence { Sequence { .term = .{ .token = "identifier" }, .suffix = .one } }, &[_]Sequence { Sequence { .term = .{ .token = "token" }, .suffix = .one } }, } }, Rule { .name = "suffix", .options = &[_][]const Sequence { &[_]Sequence { Sequence { .term = .{ .token = "?" }, .suffix = .one } }, &[_]Sequence { Sequence { .term = .{ .token = "+" }, .suffix = .one } }, &[_]Sequence { Sequence { .term = .{ .token = "*" }, .suffix = .one } }, &[_]Sequence { }, } }, }; // TODO // reverse the whitespace association, so that // whitespace (comments) before a thing are associated with that thing // and file-trailing whitespace is associate with root pub fn main() !void { // std.debug.print( "{s}\n", .{ grammar } ); for( grammar ) |rule| { std.debug.print( "{f}", .{ rule } ); } std.debug.print( "----\n", .{} ); const grammar_source = \\grammar =| _ rule* ; \\rule =| identifier _ '=' _ sequence* ';' _ ; \\sequence =| term suffix ; \\term = \\| identifier _ \\| token _ \\; \\suffix = \\| '?' \\| '+' \\| '*' \\| \\; \\ ; // _ = grammar_source; var gen = Generator.init( grammar_source ); while( try gen.next() ) |val| { // @compileLog( @TypeOf( val ) ); // std.debug.print( "{s} ({f})\n", .{ @tagName( val ), val } ); std.debug.print( "{f}\n", .{ val } ); } } const Generator = struct { src :[]const u8, i :usize, const Token = union( enum ) { whitespace :[]const u8, identifier :[]const u8, literal :u8, @"=", @"|", @";", @"?", @"+", @"*", pub fn format( x :@This(), writer :anytype ) !void { try writer.print( "Token {{ .", .{} ); switch( x ) { // .whitespace => |ws| try writer.print( "{t} = \"\"\"{s}\"\"\"", .{ ( x ), ws } ), .whitespace => |ws| try writer.print( "{t} = {q}", .{ ( x ), ws } ), .identifier => |id| try writer.print( "{t} = \"{s}\"", .{ ( x ), id } ), .literal => |li| try writer.print( "{t} = '{c}'", .{ ( x ), li } ), else => try writer.print( "symbol = '{t}'", .{ ( x ) } ), // TODO test `q` formatter .. } try writer.print( " }}", .{} ); } }; pub fn init( src :[]const u8 ) Generator { return .{ .src = src, .i = 0 }; } pub fn next( g :*Generator ) !?Token { if( !( g.i < g.src.len ) ) { return null; } const from = g.i; switch( g.src[ g.i ] ) { // whitespace ' ', '\t', '\n', '\r', '#' => { asdf: while( g.i < g.src.len ):( g.i += 1 ) { switch( g.src[ g.i ] ) { ' ', '\t', '\n', '\r' => { _ = "do nothing"; }, '#' => { while( g.i < g.src.len ):( g.i += 1 ) { if( g.src[ g.i ] == '\n' ) { break; } } // outer loop will increment beyond newline }, else => { break :asdf ; }, } } return .{ .whitespace = g.src[ from..g.i ] }; }, // symbols '=' => { g.i += 1; return .@"="; }, '|' => { g.i += 1; return .@"|"; }, ';' => { g.i += 1; return .@";"; }, // identifier 'a'...'z', 'A'...'Z', '0'...'9', '_' => { jkl: while( g.i < g.src.len ):( g.i += 1 ) { switch( g.src[ g.i ] ) { 'a'...'z', 'A'...'Z', '0'...'9', '_' => { _ = "do nothing"; }, else => { break :jkl ; }, } } return .{ .identifier = g.src[ from..g.i ] }; }, // suffixes '?' => { g.i += 1; return .@"?"; }, '+' => { g.i += 1; return .@"+"; }, '*' => { g.i += 1; return .@"*"; }, // literals '\'' => { g.i += 1; if( !( g.i < g.src.len ) ) { return error.broken_literal_1; } var val :u8 = undefined; if( g.src[ g.i ] == '\\' ) { g.i += 1; if( !( g.i < g.src.len ) ) { return error.broken_literal_2; } val = switch( g.src[ g.i ] ) { '\\' => '\\', '\'' => '\'', 'n' => '\n', 't' => '\t', 'r' => '\r', // 'x' => TODO(), // hex '\x7f' // 'u' => TODO(), // unicode '\u1234' else => g.src[ g.i ], }; } else { val = g.src[ g.i ]; } g.i += 1; if( !( g.i < g.src.len ) ) { return error.broken_literal_3; } if( g.src[ g.i ] != '\'' ) { return error.broken_literal_4; } g.i += 1; return .{ .literal = val }; }, else => { return error.bad_source; }, } @panic( "unreachable" ); } };