diff --git a/build.zig.zon b/build.zig.zon index 5b7d8a8..eeb312d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,75 +1,9 @@ .{ - // This is the default name used by packages depending on this one. For - // example, when a user runs `zig fetch --save `, this field is used - // as the key in the `dependencies` table. Although the user can choose a - // different name, most users will stick with this provided value. - // - // It is redundant to include "zig" in this name because it is already - // within the Zig package namespace. .name = .math_pl, - // This is a [Semantic Version](https://semver.org/). - // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", - // Together with name, this represents a globally unique package - // identifier. This field is generated by the Zig toolchain when the - // package is first created, and then *never changes*. This allows - // unambiguous detection of one package being an updated version of - // another. - // - // When forking a Zig project, this id should be regenerated (delete the - // field and run `zig build`) if the upstream project is still maintained. - // Otherwise, the fork is *hostile*, attempting to take control over the - // original project's identity. Thus it is recommended to leave the comment - // on the following line intact, so that it shows up in code reviews that - // modify the field. .fingerprint = 0x4abe8a4f673a8d3c, // Changing this has security and trust implications. - // Tracks the earliest Zig version that the package considers to be a - // supported use case. .minimum_zig_version = "0.15.2", - // This field is optional. - // Each dependency must either provide a `url` and `hash`, or a `path`. - // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. - // Once all dependencies are fetched, `zig build` no longer requires - // internet connectivity. - .dependencies = .{ - // See `zig fetch --save ` for a command-line interface for adding dependencies. - //.example = .{ - // // When updating this field to a new URL, be sure to delete the corresponding - // // `hash`, otherwise you are communicating that you expect to find the old hash at - // // the new URL. If the contents of a URL change this will result in a hash mismatch - // // which will prevent zig from using it. - // .url = "https://example.com/foo.tar.gz", - // - // // This is computed from the file contents of the directory of files that is - // // obtained after fetching `url` and applying the inclusion rules given by - // // `paths`. - // // - // // This field is the source of truth; packages do not come from a `url`; they - // // come from a `hash`. `url` is just one of many possible mirrors for how to - // // obtain a package matching this `hash`. - // // - // // Uses the [multihash](https://multiformats.io/multihash/) format. - // .hash = "...", - // - // // When this is provided, the package is found in a directory relative to the - // // build root. In this case the package's hash is irrelevant and therefore not - // // computed. This field and `url` are mutually exclusive. - // .path = "foo", - // - // // When this is set to `true`, a package is declared to be lazily - // // fetched. This makes the dependency only get fetched if it is - // // actually used. - // .lazy = false, - //}, - }, - // Specifies the set of files and directories that are included in this package. - // Only files and directories listed here are included in the `hash` that - // is computed for this package. Only files listed here will remain on disk - // when using the zig package manager. As a rule of thumb, one should list - // files required for compilation plus any license(s). - // Paths are relative to the build root. Use the empty string (`""`) to refer to - // the build root itself. - // A directory listed here means that all files within, recursively, are included. + .dependencies = .{}, .paths = .{ "build.zig", "build.zig.zon", diff --git a/examples/string.sub b/examples/string.sub index e89b1ef..a8cefaf 100644 --- a/examples/string.sub +++ b/examples/string.sub @@ -1 +1 @@ -print_str(""" Hello "sub" """,); +print_str("""Hello "sub\\"\nI'm a string""""",); diff --git a/src/eval.zig b/src/eval.zig index dde0636..90acab0 100644 --- a/src/eval.zig +++ b/src/eval.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const expression_pkg = @import("expression.zig"); const stdlib_pkg = @import("stdlib.zig"); @@ -70,7 +71,7 @@ pub fn sema(program: Expr, ctx: Context) void { sema(expr.lhs.*, ctx); sema(expr.rhs.*, ctx); }, - .constant, .str => {}, + .constant => {}, } }, .bool_ => |bool_expr| { @@ -108,7 +109,7 @@ pub fn sema(program: Expr, ctx: Context) void { sema(if_expr.then.*, ctx); sema(if_expr.else_.*, ctx); }, - .struct_, .var_, .void_ => {}, + .struct_, .var_, .void_, .str => {}, } } @@ -126,7 +127,10 @@ fn assert_of_type(value: Expr, ok: bool) void { value.cursor.row, value.cursor.col, }); - std.process.exit(1); + if (builtin.mode != .Debug) + std.process.exit(1) + else + panic("", .{}); } } @@ -166,7 +170,7 @@ fn add_structs(program: Expr, ctx: *Context) !void { try add_structs(arith_expr.lhs.*, ctx); try add_structs(arith_expr.rhs.*, ctx); }, - .constant, .str => {}, + .constant => {}, } }, .bool_ => |expr| { @@ -186,6 +190,7 @@ fn add_structs(program: Expr, ctx: *Context) !void { try add_structs(sub_expr, ctx); } }, + .str => {}, // if you need to add structs for more elements, // you can finish implement switch for all other types of expr else => {}, @@ -245,6 +250,7 @@ pub fn eval(expr: Expr, ctx: Context, local_vars: *Vars) Expr { }, .bind => |bind| return eval_bind(bind, ctx, local_vars), .void_ => return expr, + .str => return expr, } } @@ -259,7 +265,7 @@ fn eval_bind(bind: BindExpr, ctx: Context, local_vars: *Vars) Expr { else if (ebody.is_struct_instance()) .{ .struct_instance = ebody.as.struct_instance } else if (ebody.is_str()) - .{ .str = ebody.as.arith.str } + .{ .str = ebody.as.str } else if (ebody.is_void()) .{ .void_ = {} } else @@ -286,13 +292,16 @@ fn eval_fn_call(expr: FnCallExpr, ctx: Context, local_vars: *Vars, cursor: Curso const arg = expr.args.items[i]; const arg_name = fn_def.args.items[i]; const arg_eval = eval(arg, ctx, local_vars); - assert_of_type(arg_eval, arg_eval.tag() == .bool_ or arg_eval.tag() == .arith); + assert_of_type( + arg_eval, + arg_eval.tag() == .bool_ or arg_eval.tag() == .arith or arg_eval.tag() == .str, + ); const var_: Var = if (arg_eval.tag() == .bool_) .{ .bool_ = arg_eval.as.bool_.constant } else if (arg_eval.tag() == .arith and arg_eval.as.arith.tag() == .constant) .{ .int = arg_eval.as.arith.constant } - else if (arg_eval.tag() == .arith and arg_eval.as.arith.tag() == .str) - .{ .str = arg_eval.as.arith.str } + else if (arg_eval.tag() == .str) + .{ .str = arg_eval.as.str } else panic("unhandled case", .{}); fn_params.putNoClobber(arg_name, var_) catch unreachable; @@ -337,7 +346,7 @@ fn eval_var(expr: []const u8, ctx: Context, local_vars: *Vars, cursor: Cursor) E .file_path = ctx.file_path, }, .str => |var_| .{ - .as = .{ .arith = .{ .str = var_ } }, + .as = .{ .str = var_ }, .content = ctx.content, .cursor = cursor, .file_path = ctx.file_path, @@ -388,19 +397,25 @@ fn eval_arith(expr: ArithExpr, ctx: Context, local_vars: *Vars, cursor: Cursor) assert_of_type(rhs, rhs.as.arith == .constant); return switch (expr) { .prod => .{ - .as = .{ .arith = .{ .constant = lhs.as.arith.constant * rhs.as.arith.constant } }, + .as = .{ .arith = .{ + .constant = lhs.as.arith.constant * rhs.as.arith.constant, + } }, .content = ctx.content, .cursor = cursor, .file_path = ctx.file_path, }, .plus => .{ - .as = .{ .arith = .{ .constant = lhs.as.arith.constant + rhs.as.arith.constant } }, + .as = .{ .arith = .{ + .constant = lhs.as.arith.constant + rhs.as.arith.constant, + } }, .content = ctx.content, .cursor = cursor, .file_path = ctx.file_path, }, .minus => .{ - .as = .{ .arith = .{ .constant = lhs.as.arith.constant - rhs.as.arith.constant } }, + .as = .{ .arith = .{ + .constant = lhs.as.arith.constant - rhs.as.arith.constant, + } }, .content = ctx.content, .cursor = cursor, .file_path = ctx.file_path, @@ -408,12 +423,6 @@ fn eval_arith(expr: ArithExpr, ctx: Context, local_vars: *Vars, cursor: Cursor) else => unreachable, }; }, - .str => |str| return .{ - .as = .{ .arith = .{ .str = str } }, - .content = ctx.content, - .cursor = cursor, - .file_path = ctx.file_path, - }, } } diff --git a/src/expression.zig b/src/expression.zig index 06f00ba..c8aae9e 100644 --- a/src/expression.zig +++ b/src/expression.zig @@ -99,6 +99,7 @@ pub const ExprTag = enum { struct_, struct_instance, field_access, + str, void_, }; @@ -203,6 +204,15 @@ pub const Expr = struct { }; } + pub fn create_str(str: []const u8, l: *const Lexer) Expr { + return .{ + .as = .{ .str = str }, + .cursor = l.previous_cursor, + .content = l.content, + .file_path = l.file_path, + }; + } + pub fn create_bool(op: BoolExpr, l: *const Lexer) Expr { return .{ .as = .{ .bool_ = op }, @@ -262,7 +272,7 @@ pub const Expr = struct { pub fn is_str(self: Self) bool { if (self.tag() != .arith) return false; - return self.as.arith.tag() == .str; + return self.tag() == .str; } pub fn is_bool(self: Self) bool { @@ -295,6 +305,7 @@ pub const Expr = struct { } }, .void_ => std.debug.print("void", .{}), + .str => |expr| std.debug.print("{s}", .{expr}), .struct_ => |expr| expr.print(), .bind => |expr| expr.print(), } @@ -319,6 +330,7 @@ const ExprAs = union(ExprTag) { struct_: StructExpr, struct_instance: StructInstanceExpr, field_access: FieldAccessExpr, + str: []const u8, void_: void, }; @@ -408,14 +420,12 @@ pub const IfExpr = struct { std.debug.print("elseif (", .{}); eval.print(); std.debug.print(") ", .{}); - // std.debug.print("{{", .{}); then.print(); std.debug.print("\n", .{}); } std.debug.print("else ", .{}); self.else_.print(); - // std.debug.print("", .{}); } }; @@ -424,7 +434,6 @@ const ArithTag = enum { minus, plus, constant, - str, }; pub const ArithExpr = union(ArithTag) { @@ -434,7 +443,6 @@ pub const ArithExpr = union(ArithTag) { minus: BinOp, plus: BinOp, constant: i32, - str: []const u8, pub fn print(self: Self) void { switch (self) { @@ -442,7 +450,6 @@ pub const ArithExpr = union(ArithTag) { .minus => |expr| expr.print("-"), .plus => |expr| expr.print("+"), .constant => |expr| std.debug.print("{}", .{expr}), - .str => |expr| std.debug.print("s\"{s}\"", .{expr}), } } @@ -503,7 +510,6 @@ pub const BoolExpr = union(BoolTag) { .constant => |expr| std.debug.print("{}", .{expr}), .eql => |expr| { expr.print("=="); - // std.debug.print(")", .{}); }, .gt => |expr| { expr.print("<"); diff --git a/src/lexer.zig b/src/lexer.zig index a7e757e..7ebcb5c 100644 --- a/src/lexer.zig +++ b/src/lexer.zig @@ -331,21 +331,19 @@ pub const Lexer = struct { count += 1; _ = l.next_char(); } else { - // print("extend here???\n", .{}); - // l.name.extend(); break; } } - // print("pos here: {}, count: {}, delimiter: {}\n", .{ l.cursor.pos, count, nb_delimiter }); - if (count != nb_delimiter) { + if (count < nb_delimiter) { // This is not the end of the string // so we account the "" as in the string - // print("name: {s}\n", .{l.name.asStr(l.content)}); for (0..count) |_| { l.name.extend(); } } else { - l.name.extend(); + const nb_extend = count - nb_delimiter + 1; + for (0..nb_extend) |_| + l.name.extend(); break :loop; } } else l.name.extend(); diff --git a/src/parser.zig b/src/parser.zig index 093ddea..ea312a0 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -106,7 +106,8 @@ pub const Parser = struct { lhs.* = Expr.create_arith(.{ .constant = l.integer_value.? }, l); l.nexti(); } else if (l.token == .str) { - lhs.* = Expr.create_arith(.{ .str = l.name.as_str(l.content) }, l); + const escaped_string = try escape_row_string(alloc, l.name.as_str(l.content)); + lhs.* = Expr.create_str(escaped_string, l); l.nexti(); } else if (l.token == .bool_) { lhs.* = Expr.create_bool(.{ .constant = l.bool_value.? }, l); @@ -149,7 +150,7 @@ pub const Parser = struct { }, .str => blk: { l.nexti(); - break :blk Expr.create_arith(.{ .str = current_name }, l); + break :blk Expr.create_str(current_name, l); }, .oparen => blk: { l.nexti(); @@ -334,6 +335,30 @@ pub const Parser = struct { return Expr.create_if(eval, then, elseif_evals, elseif_thens, else_, l); } + + fn escape_row_string(alloc: Allocator, raw_str: []const u8) ![]const u8 { + var i: usize = 0; + var new_str: std.ArrayList(u8) = .empty; + + while (i < raw_str.len) { + if (raw_str[i] == '\\') { + std.debug.assert(i + 1 < raw_str.len); + const next = raw_str[i + 1]; + const to_append: u8 = switch (next) { + 'n' => '\n', + '\\' => '\\', + else => panic("unsupported escaped char {c}", .{next}), + }; + try new_str.append(alloc, to_append); + i += 1; + } else { + const next = raw_str[i]; + try new_str.append(alloc, next); + } + i += 1; + } + return new_str.items; + } }; fn arena_alloc() ArenaAllocator { @@ -392,13 +417,11 @@ test "parse print_str function" { const lhs = arg.as.arith.plus.lhs; const rhs = arg.as.arith.plus.rhs; - try expect(.arith, lhs.tag()); - try expect(.str, lhs.as.arith.tag()); - try expectStrings("bonjour", lhs.as.arith.str); + try expect(.str, lhs.tag()); + try expectStrings("bonjour", lhs.as.str); - try expect(.arith, rhs.tag()); - try expect(.str, rhs.as.arith.tag()); - try expectStrings("papa", rhs.as.arith.str); + try expect(.str, rhs.tag()); + try expectStrings("papa", rhs.as.str); } test "parse function definition" { @@ -459,14 +482,12 @@ test "parse if expression" { try expect(1, rhs.as.arith.constant); const then_node = if_expr.as.if_.then.*; - try expect(.arith, then_node.tag()); - try expect(.str, then_node.as.arith.tag()); - try expectStrings("yes", then_node.as.arith.str); + try expect(.str, then_node.tag()); + try expectStrings("yes", then_node.as.str); const else_node = if_expr.as.if_.else_.*; - try expect(.arith, else_node.tag()); - try expect(.str, else_node.as.arith.tag()); - try expectStrings("no", else_node.as.arith.str); + try expect(.str, else_node.tag()); + try expectStrings("no", else_node.as.str); } test "parse nested bind expressions" { @@ -561,9 +582,8 @@ test "parse struct instance" { try expect(.eql, cond.as.bool_.tag()); const then_branch = field_z.as.if_.then.*; - try expect(.arith, then_branch.tag()); - try expect(.str, then_branch.as.arith.tag()); - try expectStrings("bonjour", then_branch.as.arith.str); + try expect(.str, then_branch.tag()); + try expectStrings("bonjour", then_branch.as.str); const else_branch = field_z.as.if_.else_.*; try expect(.fn_call, else_branch.tag());