diff --git a/docs/parser.md b/docs/parser.md new file mode 100644 index 0000000..c1fe1ad --- /dev/null +++ b/docs/parser.md @@ -0,0 +1,44 @@ +# Parser + +The rebo parser parsers a string and produces an Abstract Syntax Tree (AST). The AST that is produced is used by all tools in the rebo ecosystem. + +```rebo-repl +> rebo.lang.parse("1 + 2 * 3", { position: true }) +{ kind: "exprs" +, value: + [ { kind: "binaryOp" + , op: "+" + , lhs: { kind: "literalInt", value: 1, position: [ 0, 1 ] } + , rhs: + { kind: "binaryOp" + , op: "*" + , lhs: { kind: "literalInt", value: 2, position: [ 4, 5 ] } + , rhs: { kind: "literalInt", value: 3, position: [ 8, 9 ] } + , position: [ 4, 9 ] + } + , position: [ 0, 9 ] + } + ] +, position: [ 0, 9 ] +} +``` + +The littering of `position` fields in the AST is optional and, going through the different scenarios, it is best to not represent them as it creates loads of clutter. + +```rebo-repl +> rebo.lang.parse("1 + 2 * 3") +{ kind: "exprs" +, value: + [ { kind: "binaryOp" + , op: "+" + , lhs: { kind: "literalInt", value: 1 } + , rhs: + { kind: "binaryOp" + , op: "*" + , lhs: { kind: "literalInt", value: 2 } + , rhs: { kind: "literalInt", value: 3 } + } + } + ] +} +``` diff --git a/src/builtins/eval.zig b/src/builtins/eval.zig index bf29cd1..249381f 100644 --- a/src/builtins/eval.zig +++ b/src/builtins/eval.zig @@ -1,20 +1,6 @@ const std = @import("std"); const Helper = @import("./helper.zig"); -fn booleanOption(stringPool: *Helper.StringPool, options: *Helper.Value, name: []const u8, default: bool) !bool { - if (options.v != Helper.ValueValue.RecordKind) { - return default; - } - - const option = try options.v.RecordKind.getU8(stringPool, name); - - if (option == null or option.?.v != Helper.ValueKind.BoolKind) { - return default; - } - - return option.?.v.BoolKind; -} - pub fn eval(machine: *Helper.Runtime, numberOfArgs: usize) !void { const code = try Helper.getArgument(machine, numberOfArgs, 0, &[_]Helper.ValueKind{Helper.ValueValue.StringKind}); const scope = try Helper.getArgument(machine, numberOfArgs, 1, &[_]Helper.ValueKind{Helper.ValueValue.ScopeKind}); diff --git a/src/builtins/helper.zig b/src/builtins/helper.zig index fe2cec4..1a98738 100644 --- a/src/builtins/helper.zig +++ b/src/builtins/helper.zig @@ -58,3 +58,17 @@ pub fn getArgument(runtime: *Runtime, numberOfArgs: usize, position: usize, expe return runtime.unitValue.?; } + +pub fn booleanOption(stringPool: *StringPool, options: *Value, name: []const u8, default: bool) !bool { + if (options.v != ValueValue.RecordKind) { + return default; + } + + const option = try options.v.RecordKind.getU8(stringPool, name); + + if (option == null or option.?.v != ValueKind.BoolKind) { + return default; + } + + return option.?.v.BoolKind; +} diff --git a/src/builtins/open.zig b/src/builtins/open.zig index f552bb6..40ae378 100644 --- a/src/builtins/open.zig +++ b/src/builtins/open.zig @@ -1,16 +1,6 @@ const std = @import("std"); const Helper = @import("./helper.zig"); -fn booleanOption(stringPool: *Helper.StringPool, options: *Helper.Value, name: []const u8, default: bool) !bool { - const option = try options.v.RecordKind.getU8(stringPool, name); - - if (option == null or option.?.v != Helper.ValueKind.BoolKind) { - return default; - } - - return option.?.v.BoolKind; -} - pub fn open(machine: *Helper.Runtime, numberOfArgs: usize) !void { const path = (try Helper.getArgument(machine, numberOfArgs, 0, &[_]Helper.ValueKind{Helper.ValueValue.StringKind})).v.StringKind.slice(); const options = try Helper.getArgument(machine, numberOfArgs, 1, &[_]Helper.ValueKind{ Helper.ValueValue.RecordKind, Helper.ValueValue.UnitKind }); @@ -20,11 +10,11 @@ pub fn open(machine: *Helper.Runtime, numberOfArgs: usize) !void { return; } - const readF = try booleanOption(machine.stringPool, options, "read", false); - const writeF = try booleanOption(machine.stringPool, options, "write", false); - const appendF = try booleanOption(machine.stringPool, options, "append", false); - const truncateF = try booleanOption(machine.stringPool, options, "truncate", false); - const createF = try booleanOption(machine.stringPool, options, "create", false); + const readF = try Helper.booleanOption(machine.stringPool, options, "read", false); + const writeF = try Helper.booleanOption(machine.stringPool, options, "write", false); + const appendF = try Helper.booleanOption(machine.stringPool, options, "append", false); + const truncateF = try Helper.booleanOption(machine.stringPool, options, "truncate", false); + const createF = try Helper.booleanOption(machine.stringPool, options, "create", false); if (createF) { const file = std.fs.cwd().createFile(path, .{ .read = readF, .truncate = truncateF, .exclusive = false }) catch |err| return Helper.raiseOsError(machine, "open", err); diff --git a/src/builtins/parser.zig b/src/builtins/parser.zig new file mode 100644 index 0000000..196b6dc --- /dev/null +++ b/src/builtins/parser.zig @@ -0,0 +1,82 @@ +const std = @import("std"); + +const AST = @import("./../ast.zig"); +const Helper = @import("./helper.zig"); +const Parser = @import("./../ast-interpreter.zig"); + +pub fn parse(machine: *Helper.Runtime, numberOfArgs: usize) !void { + const v = try Helper.getArgument(machine, numberOfArgs, 0, &[_]Helper.ValueKind{Helper.ValueValue.StringKind}); + const options = try Helper.getArgument(machine, numberOfArgs, 1, &[_]Helper.ValueKind{ Helper.ValueValue.RecordKind, Helper.ValueValue.UnitKind }); + + const ast = try Parser.parse(machine, "input", v.v.StringKind.slice()); + defer ast.destroy(machine.allocator); + + const position = try Helper.booleanOption(machine.stringPool, options, "position", false); + + try emit(machine, ast, position); +} +const pos = Helper.Errors.Position{ .start = 0, .end = 0 }; + +fn emit(machine: *Helper.Runtime, ast: *AST.Expression, position: bool) !void { + switch (ast.kind) { + .binaryOp => { + try machine.pushEmptyRecordValue(); + + try machine.pushStringValue("kind"); + try machine.pushStringValue("binaryOp"); + try machine.setRecordItemBang(pos); + + try machine.pushStringValue("op"); + try machine.pushStringValue(ast.kind.binaryOp.op.toString()); + try machine.setRecordItemBang(pos); + + try machine.pushStringValue("lhs"); + try emit(machine, ast.kind.binaryOp.left, position); + try machine.setRecordItemBang(pos); + + try machine.pushStringValue("rhs"); + try emit(machine, ast.kind.binaryOp.right, position); + try machine.setRecordItemBang(pos); + }, + .exprs => { + try machine.pushEmptyRecordValue(); + + try machine.pushStringValue("kind"); + try machine.pushStringValue("exprs"); + try machine.setRecordItemBang(pos); + + try machine.pushStringValue("value"); + try machine.pushEmptySequenceValue(); + for (ast.kind.exprs) |expr| { + try emit(machine, expr, position); + try machine.appendSequenceItemBang(pos); + } + try machine.setRecordItemBang(pos); + }, + .literalInt => { + try machine.pushEmptyRecordValue(); + + try machine.pushStringValue("kind"); + try machine.pushStringValue("literalInt"); + try machine.setRecordItemBang(pos); + + try machine.pushStringValue("value"); + try machine.pushIntValue(ast.kind.literalInt); + try machine.setRecordItemBang(pos); + }, + else => { + std.io.getStdErr().writer().print("unreachable: {}\n", .{ast.kind}) catch {}; + unreachable; + }, + } + + if (position) { + try machine.pushStringValue("position"); + try machine.pushEmptySequenceValue(); + try machine.pushIntValue(@intCast(ast.position.start)); + try machine.appendSequenceItemBang(pos); + try machine.pushIntValue(@intCast(ast.position.end)); + try machine.appendSequenceItemBang(pos); + try machine.setRecordItemBang(pos); + } +} diff --git a/src/runtime.zig b/src/runtime.zig index f7cf873..f4f5287 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -1608,6 +1608,7 @@ fn setupRebo(state: *Runtime) !void { try reboLang.v.RecordKind.setU8(state.stringPool, "float", try state.newBuiltinValue(@import("builtins/float.zig").float)); try reboLang.v.RecordKind.setU8(state.stringPool, "keys", try state.newBuiltinValue(@import("builtins/keys.zig").keys)); try reboLang.v.RecordKind.setU8(state.stringPool, "len", try state.newBuiltinValue(@import("builtins/len.zig").len)); + try reboLang.v.RecordKind.setU8(state.stringPool, "parse", try state.newBuiltinValue(@import("builtins/parser.zig").parse)); try reboLang.v.RecordKind.setU8(state.stringPool, "scope", try state.newBuiltinValue(@import("builtins/scope.zig").scope)); try reboLang.v.RecordKind.setU8(state.stringPool, "scope.bind!", try state.newBuiltinValue(@import("builtins/scope.zig").bind)); try reboLang.v.RecordKind.setU8(state.stringPool, "scope.delete!", try state.newBuiltinValue(@import("builtins/scope.zig").delete));