diff options
| -rw-r--r-- | .envrc | 1 | ||||
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | README.md | 145 | ||||
| -rw-r--r-- | build.zig | 39 | ||||
| -rw-r--r-- | build.zig.zon | 15 | ||||
| -rw-r--r-- | eostre/build.zig | 20 | ||||
| -rw-r--r-- | eostre/build.zig.zon | 10 | ||||
| -rw-r--r-- | eostre/lib/cursor.zig | 69 | ||||
| -rw-r--r-- | eostre/lib/parser/common.zig | 28 | ||||
| -rw-r--r-- | eostre/lib/parser/context.zig | 9 | ||||
| -rw-r--r-- | eostre/lib/parser/root.zig | 284 | ||||
| -rw-r--r-- | eostre/lib/root.zig | 8 | ||||
| -rw-r--r-- | flake.lock | 27 | ||||
| -rw-r--r-- | flake.nix | 18 | ||||
| -rw-r--r-- | src/main.zig | 157 |
15 files changed, 833 insertions, 0 deletions
@@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6781a58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.direnv +.zig-cache +zig-out diff --git a/README.md b/README.md new file mode 100644 index 0000000..0c16cd8 --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ +# Implementation of *Ēostre’s Shell* + +# Syntax +## Piping +```esh +program_0 | program_1 | program_2 | ... | program_n +``` + +## Comments +```esh +# Comments are written like this +``` + +## Forcing Pipe Mode +### Force String +```esh +mode(string) { program_0 | program_1 } | program_2 | program_3 +``` +### Force *EAPM* +```esh +unsafe { + mode(raw) { program_0 | program_1 } | program_2 | program_3 +} +``` + +Forcing `EAPM` can only happen in a unsafe block. +All pipes in a forced mode scope will be forced to this mode. +In this example `program_0` and `program_1` will run in the forced +mode. + +## If-Statement +```esh +if (program arg_1 arg_2 ... arg_n) { + # do something here +} +``` +```esh +if (program arg_1 arg_2 ... arg_n) { + # do something here +} else { + # do something else +} +``` +```esh +if (program0 arg_1 arg_2 ... arg_n) { + # do something here +} else if program1 arg_1 arg_2 ... arg_n { + # do something here +} else { + # do something else +} +``` + +## For-Loop +For loops are always *line-based* +```esh +for line in (program arg_1 arg_2 ... arg_n) { + # do something here +} +``` + +## While-Loop +```esh +while (program arg_1 arg_2 ... arg_n) { + # do something here +} +``` + +## Variables +### Set +```esh +var = { + program arg_1 arg_2 ... arg_n +} +``` + +### Get +This will pass `var` as `arg_1` +``` esh +program arg_1 (var) arg_3 ... arg_n +``` + +## String +### Normal String +``` esh +var = "Some random string" +``` + +#### Embedding other variables and program calls into string definition. +``` esh +var2 = "this is var: (var) and program gives the output: {program arg_1 arg_2 ... arg_n}" +``` +If you want to use `{` or `}` as a real character use the escape character `\` or use a raw +string: + +``` esh +var = "The following is a curly bracket: \{" +``` + +### Raw String +A raw string is a string where no character except the end character +is treated special +``` esh +var = 'Some string where I can use " without ending the string' +``` + +## Functions +``` esh +fn my-function(arg_1 arg_2 arg_3) { + # do something here +} +``` + +### `Use`-Keyword + +The `use` keyword passes a variable from the parent to the child scope. +If the variable does not exist in the parent context yet it will +set it to a empty string. + +``` esh +fn my-function(arg_1 arg_2 arg_3) use (var0 var1) { + # do something here +} +``` + +## Multi-Line Expressions +``` esh +program arg_1 \ + arg_2 \ + ... + arg_n +``` + +## Merging Lines +``` esh +program0 arg_1 arg_2 ... arg_n; program1 arg_1 arg_2 ... arg_n +``` + +``` esh +program0 && program1 +``` + +``` esh +program0 || program1 +``` diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..40cae2d --- /dev/null +++ b/build.zig @@ -0,0 +1,39 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const pkg = b.dependency("eostre", .{ + .target = target, + .optimize = optimize, + }); + + const exe = b.addExecutable(.{ + .name = "shell", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize + }); + + const run_exe = b.addRunArtifact(exe); + const run_step = b.step("run", "Run the application"); + run_step.dependOn(&run_exe.step); + + exe.root_module.addImport("eostre", pkg.module("eostre")); + + const test_step = b.step("test", "Run unit tests"); + + const tests = b.addTest(.{ + .name = "shell", + .root_source_file = b.path("src/main.zig"), + .target = target, + }); + tests.root_module.addImport("eostre", pkg.module("eostre")); + + const run_tests = b.addRunArtifact(tests); + test_step.dependOn(&run_tests.step); + + const lib_tests = b.addRunArtifact(pkg.artifact("eostre")); + test_step.dependOn(&lib_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..311d2b8 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,15 @@ +.{ + .name = "shell", + .version = "0.0.0", + .dependencies = .{ + .eostre = .{ + .path = "./eostre", + }, + }, + + .paths = .{ + "src", + "build.zig", + "build.zig.zon" + }, +} diff --git a/eostre/build.zig b/eostre/build.zig new file mode 100644 index 0000000..da302f9 --- /dev/null +++ b/eostre/build.zig @@ -0,0 +1,20 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + _ = b.addModule("eostre", .{ + .root_source_file = b.path("lib/root.zig"), + .target = target, + .optimize = optimize, + }); + + const tests = b.addTest(.{ + .name = "eostre", + .root_source_file = b.path("lib/root.zig"), + .target = target, + }); + + b.installArtifact(tests); +} diff --git a/eostre/build.zig.zon b/eostre/build.zig.zon new file mode 100644 index 0000000..39514b6 --- /dev/null +++ b/eostre/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = "eostre", + .version = "0.0.0", + + .paths = .{ + "lib", + "build.zig", + "build.zig.zon" + } +} diff --git a/eostre/lib/cursor.zig b/eostre/lib/cursor.zig new file mode 100644 index 0000000..c0a4a32 --- /dev/null +++ b/eostre/lib/cursor.zig @@ -0,0 +1,69 @@ +const std = @import("std"); + +pub const ReadError = error { + EndOfBuffer +}; + +pub fn Cursor(comptime T: type) type { + return struct { + const Self = @This(); + + buffer: []const T, + index: usize, + touched_index: usize, + + pub fn init(buffer: []const T) Self { + return .{ + .buffer = buffer, + .index = 0, + .touched_index = 0, + }; + } + + pub fn peek(self: Self, amount: usize) ReadError![]const T { + if (self.index + amount > self.buffer.len) { + return ReadError.EndOfBuffer; + } + + return self.buffer[self.index..self.index + amount]; + } + + pub fn consume(self: *Self, amount: usize) ReadError![]const T { + const slice = try self.peek(amount); + self.index += amount; + self.touched_index = self.index; + return slice; + } + + pub fn snapshot(self: Self) usize { + return self.index; + } + + pub fn rollback(self: *Self, index: usize) void { + self.index = index; + } + + pub fn touched_slice(self: Self) []const T { + return self.buffer[0..self.touched_index]; + } + }; +} + +test "peek" { + const buffer = "1234"; + const cursor = Cursor(u8).init(buffer); + try std.testing.expectEqualSlices(u8, "1", try cursor.peek(1)); + try std.testing.expectEqualSlices(u8, "12", try cursor.peek(2)); + try std.testing.expectEqualSlices(u8, "123", try cursor.peek(3)); + try std.testing.expectEqualSlices(u8, "1234", try cursor.peek(4)); + try std.testing.expectError(ReadError.EndOfBuffer, cursor.peek(5)); +} + +test "consume" { + const buffer = "12345678"; + var cursor = Cursor(u8).init(buffer); + try std.testing.expectEqualSlices(u8, "1", try cursor.consume(1)); + try std.testing.expectEqualSlices(u8, "23", try cursor.consume(2)); + try std.testing.expectEqualSlices(u8, "456", try cursor.consume(3)); + try std.testing.expectError(ReadError.EndOfBuffer, cursor.consume(4)); +} diff --git a/eostre/lib/parser/common.zig b/eostre/lib/parser/common.zig new file mode 100644 index 0000000..986af58 --- /dev/null +++ b/eostre/lib/parser/common.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const root = @import("root.zig"); +const Context = @import("context.zig").Context; +const ParseFn = root.ParseFn; + +pub fn LiteralFn(comptime I: type, comptime O: type) type { + return *const fn (comptime literal: []const I, value: O) ParseFn(I, O); +} + +pub fn Literal(comptime I: type, comptime O: type) LiteralFn(I, O) { + return struct { + pub fn literal(comptime lit: []const I, value: O) ParseFn(I, O) { + return struct { + pub fn parse(ctx: *Context(I)) !O { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + const slice = try ctx.cursor.consume(lit.len); + if (std.mem.eql(I, lit, slice)) { + return value; + } + + return error.UnexpectedLiteral; + } + }.parse; + } + }.literal; +} diff --git a/eostre/lib/parser/context.zig b/eostre/lib/parser/context.zig new file mode 100644 index 0000000..56fb025 --- /dev/null +++ b/eostre/lib/parser/context.zig @@ -0,0 +1,9 @@ +const std = @import("std"); +const Cursor = @import("../cursor.zig").Cursor; + +pub fn Context(comptime I: type) type { + return struct { + cursor: Cursor(I), + allocator: std.mem.Allocator, + }; +} diff --git a/eostre/lib/parser/root.zig b/eostre/lib/parser/root.zig new file mode 100644 index 0000000..b168a88 --- /dev/null +++ b/eostre/lib/parser/root.zig @@ -0,0 +1,284 @@ +const std = @import("std"); +const Cursor = @import("../cursor.zig").Cursor; +pub const common = @import("common.zig"); +pub const Context = @import("context.zig").Context; + + +pub fn ParseFn(comptime I: type, comptime O: type) type { + return *const fn (ctx: *Context(I)) anyerror!O; +} + +pub fn Parser(comptime I: type, comptime O: type, comptime parse_fn: ParseFn(I, O)) type { + return struct { + const Self = @This(); + + pub fn parse(ctx: *Context(I)) !O { + return try parse_fn(ctx); + } + + pub fn or_else(comptime other: ParseFn(I, O)) type { + return Parser(I, O, struct { + pub fn parse(ctx: *Context(I)) !O { + return parse_fn(ctx) catch other(ctx); + } + }.parse); + } + + pub fn and_skip(comptime skip: anytype) type { + return Parser(I, O, struct { + pub fn parse(ctx: *Context(I)) !O { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + _ = try skip(ctx); + return try parse_fn(ctx); + } + }.parse); + } + + pub fn then_skip(comptime skip: anytype) type { + return Parser(I, O, struct { + pub fn parse(ctx: *Context(I)) !O { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + const value = try parse_fn(ctx); + _ = try skip(ctx); + return value; + } + }.parse); + } + + pub fn then(comptime O2: type, comptime other: ParseFn(I, O2)) type { + const CombinedOutput = struct { O, O2 }; + + return Parser(I, CombinedOutput, struct { + pub fn parse(ctx: *Context(I)) !CombinedOutput { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + return .{ try parse_fn(ctx), try other(ctx) }; + } + }.parse); + } + + pub fn zero_or_more() type { + return Parser(I, std.ArrayList(O), struct { + pub fn parse(ctx: *Context(I)) !std.ArrayList(O) { + var values = std.ArrayList(O).init(ctx.allocator); + errdefer values.deinit(); + + while (parse_fn(ctx)) |value| { + try values.append(value); + } else |_| {} + + return values; + } + }.parse); + } + + pub fn repeat(n: usize) type { + return Parser(I, [n]O, struct { + pub fn parse(ctx: *Context(I)) ![n]O { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + var values: [n]O = undefined; + + for (&values) |*value| { + value.* = try parse_fn(ctx); + } + + return values; + } + }.parse); + } + + pub fn map(M: type, map_fn: *const fn (value: O) M) type { + return Parser(I, M, struct { + pub fn parse(ctx: *Context(I)) !M { + return map_fn(try parse_fn(ctx)); + } + }.parse); + } + + pub fn end() type { + return Parser(I, O, struct { + pub fn parse(ctx: *Context(I)) !O { + const value = try parse_fn(ctx); + _ = ctx.cursor.peek(1) catch return value; + return error.NotEnd; + } + }.parse); + } + }; +} + +fn parse_zero(ctx: *Context(u8)) !u8 { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + const value = try ctx.cursor.consume(1); + + if (value[0] == '0') { + return 0; + } + + return error.NotZero; +} + +fn parse_one(ctx: *Context(u8)) !u8 { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + const value = try ctx.cursor.consume(1); + + if (value[0] == '1') { + return 1; + } + + return error.NotOne; +} + + +test "direct" { + const ZeroParser = Parser(u8, u8, parse_zero); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("0"), + .allocator = std.testing.allocator, + }; + try std.testing.expectEqual(0, try ZeroParser.parse(&ctx)); + + ctx.cursor = Cursor(u8).init("1"); + try std.testing.expectError(error.NotZero, ZeroParser.parse(&ctx)); +} + +test "or_else" { + const ZeroParser = Parser(u8, u8, parse_zero); + const ZeroAndOneParser = ZeroParser.or_else(parse_one); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("0"), + .allocator = std.testing.allocator, + }; + try std.testing.expectEqual(0, try ZeroAndOneParser.parse(&ctx)); + + ctx.cursor = Cursor(u8).init("1"); + try std.testing.expectEqual(1, try ZeroAndOneParser.parse(&ctx)); + + ctx.cursor = Cursor(u8).init("2"); + try std.testing.expectError(error.NotZero, ZeroParser.parse(&ctx)); +} + +test "then" { + const ZeroParser = Parser(u8, u8, parse_zero); + const ZeroThenOneParser = ZeroParser.then(u8, parse_one); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("01"), + .allocator = std.testing.allocator, + }; + try std.testing.expectEqual(.{ 0, 1 }, try ZeroThenOneParser.parse(&ctx)); + + ctx.cursor = Cursor(u8).init("11"); + try std.testing.expectError(error.NotZero, ZeroThenOneParser.parse(&ctx)); + + ctx.cursor = Cursor(u8).init("00"); + try std.testing.expectError(error.NotOne, ZeroThenOneParser.parse(&ctx)); +} + +test "zero_or_more" { + const ZeroParser = Parser(u8, u8, parse_zero); + const ZeroWildcardParser = ZeroParser.zero_or_more(); + const ZeroOrOneWildcardParser = ZeroParser.or_else(parse_one).zero_or_more(); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("0000"), + .allocator = std.testing.allocator, + }; + var result = try ZeroWildcardParser.parse(&ctx); + try std.testing.expectEqualSlices(u8, &[_]u8{0, 0, 0, 0}, result.items); + result.deinit(); + + ctx.cursor = Cursor(u8).init("00001"); + result = try ZeroWildcardParser.parse(&ctx); + try std.testing.expectEqualSlices(u8, &[_]u8{0, 0, 0, 0}, result.items); + result.deinit(); + + ctx.cursor = Cursor(u8).init("0101110"); + result = try ZeroOrOneWildcardParser.parse(&ctx); + try std.testing.expectEqualSlices(u8, &[_]u8{0, 1, 0, 1, 1, 1, 0}, result.items); + result.deinit(); + + ctx.cursor = Cursor(u8).init("0101110abc"); + result = try ZeroOrOneWildcardParser.parse(&ctx); + try std.testing.expectEqualSlices(u8, &[_]u8{0, 1, 0, 1, 1, 1, 0}, result.items); + result.deinit(); +} + +test "repeat" { + const ZeroParser = Parser(u8, u8, parse_zero); + const FourZerosParser = ZeroParser.repeat(4); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("00001"), + .allocator = std.testing.allocator, + }; + try std.testing.expectEqualSlices( + u8, + &[_]u8{0, 0, 0, 0}, + &(try FourZerosParser.parse(&ctx)) + ); + + ctx.cursor = Cursor(u8).init("0001"); + try std.testing.expectError( + error.NotZero, + FourZerosParser.parse(&ctx) + ); +} + +test "map" { + const ZeroOrOneParser = Parser(u8, u8, parse_zero).or_else(parse_one); + const ZeroOrOneMappedParser = ZeroOrOneParser.map(u8, struct { + pub fn map(i: u8) u8 { + return i * 2; + } + }.map).zero_or_more(); + + var ctx = Context(u8) { + .cursor = Cursor(u8).init("00110101"), + .allocator = std.testing.allocator, + }; + const result = try ZeroOrOneMappedParser.parse(&ctx); + try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0, 2, 2, 0, 2, 0, 2 }, result.items); + defer result.deinit(); +} + +test "cursor rollback" { + const DoubleZeroOrOneParser = Parser(u8, u8, parse_zero).repeat(3).or_else( + Parser(u8, u8, parse_zero).repeat(2).then(u8, parse_one).map([3]u8, struct { + pub fn map(r: struct { [2]u8, u8 }) [3]u8 { + const array, const last = r; + return array ++ .{ last }; + } + }.map).parse + ); + var ctx = Context(u8) { + .cursor = Cursor(u8).init("0001"), + .allocator = std.testing.allocator, + }; + try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0, 0 }, &(try DoubleZeroOrOneParser.parse(&ctx))); + + ctx.cursor = Cursor(u8).init("0010"); + try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0, 1 }, &(try DoubleZeroOrOneParser.parse(&ctx))); +} + +test "cursor error touched slice" { + const OneThenDoubleZeroParser = Parser(u8, u8, parse_one).then([2]u8, Parser(u8, u8, parse_zero).repeat(2).parse); + var ctx = Context(u8) { + .cursor = Cursor(u8).init("10110"), + .allocator = std.testing.allocator, + }; + try std.testing.expectError(error.NotZero, OneThenDoubleZeroParser.parse(&ctx)); + try std.testing.expectEqualSlices(u8, "101", ctx.cursor.touched_slice()); +} diff --git a/eostre/lib/root.zig b/eostre/lib/root.zig new file mode 100644 index 0000000..86af090 --- /dev/null +++ b/eostre/lib/root.zig @@ -0,0 +1,8 @@ +const std = @import("std"); + +pub const cursor = @import("cursor.zig"); +pub const parser = @import("parser/root.zig"); + +test { + std.testing.refAllDecls(@This()); +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..fbd8c73 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1737299813, + "narHash": "sha256-Qw2PwmkXDK8sPQ5YQ/y/icbQ+TYgbxfjhgnkNJyT1X8=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "107d5ef05c0b1119749e381451389eded30fb0d5", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..8ffc51e --- /dev/null +++ b/flake.nix @@ -0,0 +1,18 @@ +{ + description = "Parz"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-24.11"; + }; + + outputs = { self, nixpkgs }: + let + pkgs = import nixpkgs { system = "x86_64-linux"; }; + in + { + devShells.x86_64-linux.default = pkgs.zigStdenv.mkDerivation { + name = "zig-dev-shell"; + buildInputs = [ pkgs.zig ]; + }; + }; +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..2144798 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,157 @@ +const std = @import("std"); +const eostre = @import("eostre"); +const Cursor = eostre.cursor.Cursor; +const Parser = eostre.parser.Parser; +const Context = eostre.parser.Context; +const Literal = eostre.parser.common.Literal; +const ParseFn = eostre.parser.ParseFn; + +const Keyword = enum { + if_statement, + other +}; + +const String = struct { +}; + +const Token = union(enum) { + openparenthesis: void, + closeparenthesis: void, + openscope: void, + closescope: void, + backslash: void, + pipe: void, + semicolon: void, + logical_and: void, + logical_or: void, + assign: void, + keyword: Keyword, + string: String, +}; + +pub fn parse_string(ctx: *Context(u8)) !Token { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + const first_char = (try ctx.cursor.consume(1))[0]; + + if (first_char == ' ') { + return error.NotAString; + } + + if (first_char == '"') { + while (ctx.cursor.consume(1) catch null) |c| { + if (c[0] == '\"') { + return Token.string; + } else if (c[0] == '{') { + while (ctx.cursor.consume(1) catch null) |inner| { + if (inner[0] == '}') { + break; + } + } else { + return error.UnterminatedString; + } + } + } else { + return error.UnterminatedString; + } + } else if (first_char == '\'') { + while (ctx.cursor.consume(1) catch null) |c| { + if (c[0] == '\'') { + return Token.string; + } + } else { + return error.UnterminatedString; + } + } + + while (ctx.cursor.consume(1) catch null) |c| { + if (c[0] == ' ') { + return Token.string; + } else if (c[0] == '{') { + while (ctx.cursor.consume(1) catch null) |inner| { + if (inner[0] == '}') { + break; + } + } else { + return error.UnterminatedString; + } + } + } else { + return error.UnterminatedString; + } +} + +const unterminated_keyword = Parser(u8, Keyword, keyword_literal("if", Keyword.if_statement)); + +fn keyword(ctx: *Context(u8)) !Token { + const snapshot = ctx.cursor.snapshot(); + errdefer ctx.cursor.rollback(snapshot); + + const token = try unterminated_keyword.parse(ctx); + const next_char = (try ctx.cursor.peek(1))[0]; + + switch (next_char) { + '(', ')', '{', '}', '\\', '&', '|', ';', '=', ' ' => return .{ .keyword = token }, + else => return error.UnterminatedKeyword, + } +} + +const literal = Literal(u8, Token); +const keyword_literal = Literal(u8, Keyword); +const skip_literal = Literal(u8, void); + +const Tokenizer = Parser(u8, Token, literal("(", Token.openparenthesis)) + .or_else(literal(")", Token.closeparenthesis)) + .or_else(literal("{", Token.openscope)) + .or_else(literal("}", Token.closescope)) + .or_else(literal("\\", Token.backslash)) + .or_else(literal("&&", Token.logical_and)) + .or_else(literal("||", Token.logical_or)) + .or_else(literal("|", Token.pipe)) + .or_else(literal(";", Token.semicolon)) + .or_else(literal("=", Token.assign)) + .or_else(keyword) + .or_else(parse_string) + .and_skip(Parser(u8, void, skip_literal(" ", {})).zero_or_more().parse) + .zero_or_more() + .then_skip(Parser(u8, void, skip_literal(" ", {})).zero_or_more().parse) + .end(); + +pub fn main() void { + std.debug.print("Hello, World!\n", .{}); +} + +test "tokenizer" { + const example = " | 'asdf' ( || \"hello\" ) or_something &&{ =}\\ ; if "; + + var ctx = Context(u8) { + .cursor = Cursor(u8).init(example), + .allocator = std.testing.allocator, + }; + + const tokens = try Tokenizer.parse(&ctx); + defer tokens.deinit(); + + + try std.testing.expectEqualSlices( + Token, + &[_]Token { + .pipe, + .string, + .openparenthesis, + .logical_or, + .string, + .closeparenthesis, + .string, + .logical_and, + .openscope, + .assign, + .closescope, + .backslash, + .semicolon, + .{ .keyword = Keyword.if_statement } + }, + tokens.items + ); +} |