18 files changed, 264 insertions(+), 87 deletions(-)
.gitignore+1-1| ... | ... | @@ -6,5 +6,5 @@ |
| 6 | 6 | # excludesfile = ~/.gitignore |
| 7 | 7 | # |
| 8 | 8 | |
| 9 | .zig-cache | |
| 9 | 10 | zig-out |
| 10 | zig-cache |
build.zig+1-1| ... | ... | @@ -127,7 +127,7 @@ pub fn build(b: *std.Build) void { |
| 127 | 127 | package.dependOn(&b.addInstallFile( |
| 128 | 128 | artifact.getEmittedBin(), |
| 129 | 129 | b.pathJoin(&.{ |
| 130 | "release", | |
| 130 | "package", | |
| 131 | 131 | name, |
| 132 | 132 | std.mem.concat(b.allocator, u8, &.{ |
| 133 | 133 | bin_name, |
build.zig.zon+3-3| ... | ... | @@ -2,12 +2,12 @@ |
| 2 | 2 | .name = "", |
| 3 | 3 | .version = "0.0.0", |
| 4 | 4 | |
| 5 | .minimum_zig_version = "0.12.0", | |
| 5 | .minimum_zig_version = "0.13.0-dev.348+c564a16a0", | |
| 6 | 6 | |
| 7 | 7 | .dependencies = .{ |
| 8 | 8 | .zg = .{ |
| 9 | .url = "https://codeberg.org/dude_the_builder/zg/archive/16735685fcc3410de361ba3411788ad1fb4fe188.tar.gz", | |
| 10 | .hash = "1220fe9ac5cdb41833d327a78745614e67d472469f8666567bd8cf9f5847a52b1c51", | |
| 9 | .url = "https://codeberg.org/dude_the_builder/zg/archive/c425c9c8511bf92e14b8b612d1d16e774b186f2e.tar.gz", | |
| 10 | .hash = "1220c79009f1e443b015db4de6e5319432538ead2fea10d271f3f35d3ae83c996789", | |
| 11 | 11 | }, |
| 12 | 12 | .tracer = .{ |
| 13 | 13 | .url = "https://github.com/paperdave/zig-tracer/archive/d4c2eff040e9e69b9f08d2175b315f3055ad7ba3.tar.gz", |
doc/language-reference.md+14-11| ... | ... | @@ -1,10 +1,6 @@ |
| 1 | 1 | # New Language Project |
| 2 | 2 | |
| 3 | This is a high-level, general purpose programming language. It is a mix of Rust, Zig, and JavaScript's great points. | |
| 4 | ||
| 5 | - **Rust** - Expressive control flow that is easy to write. | |
| 6 | - **Zig** - Execute any code at compile time, everything is an expression. | |
| 7 | - **JavaScript** - Garbage collected values with a versitile type system. | |
| 3 | This is a high-level, general purpose programming language. | |
| 8 | 4 | |
| 9 | 5 | Key goals of this project are: |
| 10 | 6 | |
| ... | ... | @@ -12,6 +8,12 @@ Key goals of this project are: |
| 12 | 8 | - **Fast Iteration**: The compiler is designed for quick incremental compilation and hot module reloading. |
| 13 | 9 | - TODO: some more |
| 14 | 10 | |
| 11 | It primarily focuses on merging some great points of Rust, Zig, and JavaScript's language designs: | |
| 12 | ||
| 13 | - **Rust** - Expressive control flow that is easy to write. | |
| 14 | - **Zig** - Execute any code at compile time, everything is an expression. | |
| 15 | - **JavaScript** - Garbage collected values with a versitile type system. | |
| 16 | ||
| 15 | 17 | ## Standard Library |
| 16 | 18 | |
| 17 | 19 | Currently, there is no standard library. One will be introduced later. |
| ... | ... | @@ -74,7 +76,7 @@ let hello = hello * world; |
| 74 | 76 | var message = "first message"; |
| 75 | 77 | |
| 76 | 78 | if @js("Math").random() > 0.5 { |
| 77 | message = "second message" | |
| 79 | message = "second message"; | |
| 78 | 80 | } |
| 79 | 81 | |
| 80 | 82 | @js("console").log(message); |
| ... | ... | @@ -110,15 +112,14 @@ fn add(a: int, b: int) -> int { |
| 110 | 112 | } |
| 111 | 113 | |
| 112 | 114 | let print = @js("console").log; |
| 113 | //print("Sum of 2 and 7" + add(2, 7)); | |
| 114 | print(add(2, 7)); | |
| 115 | print("Sum of 2 and 7" + add(2, 7)); | |
| 115 | 116 | ``` |
| 116 | 117 | |
| 117 | 118 | > **Bug**: Return types are currently required. Omitting the `-> type` syntax currently makes the return type `void`, but this will be changed in the future to be an inferred return type. |
| 118 | 119 | |
| 119 | 120 | The `return` expression can be used within functions to return early. |
| 120 | 121 | |
| 121 | ```lang | |
| 122 | ```lang:run | |
| 122 | 123 | fn something(a: int) -> bool { |
| 123 | 124 | if a == 0 { |
| 124 | 125 | return false; |
| ... | ... | @@ -126,6 +127,9 @@ fn something(a: int) -> bool { |
| 126 | 127 | |
| 127 | 128 | a > 100 |
| 128 | 129 | } |
| 130 | ||
| 131 | let print = @js("console").log; | |
| 132 | print(something(0xFF), something(0)); | |
| 129 | 133 | ``` |
| 130 | 134 | |
| 131 | 135 | ## Number Literals |
| ... | ... | @@ -143,11 +147,10 @@ A global variable handle can be retrived with `@js`. We've seen this in the hell |
| 143 | 147 | Many JS-exclusive actions are exposed via built-in functions, such as using `@jsNew` for calling constructors. |
| 144 | 148 | |
| 145 | 149 | ```lang:run |
| 146 | let date_constructor = @js("Date"); | |
| 147 | 150 | let print = @js("console").log; |
| 148 | 151 | |
| 149 | 152 | fn currentYear() -> int { |
| 150 | @jsNew(date_constructor).getFullYear() | |
| 153 | @jsNew(@js("Date")).getFullYear() | |
| 151 | 154 | } |
| 152 | 155 | |
| 153 | 156 | print(currentYear()); |
src/core/Air.Dump.zig+2-2| ... | ... | @@ -126,8 +126,8 @@ pub const State = struct { |
| 126 | 126 | try tty.setColor(w, .reset); |
| 127 | 127 | try w.writeAll(")"); |
| 128 | 128 | }, |
| 129 | .call => { | |
| 130 | const call: Air.Call = air.instData(.call, inst); | |
| 129 | inline .call, .new => |t| { | |
| 130 | const call: Air.Call = air.instData(t, inst); | |
| 131 | 131 | const call_data = call.call_data.get(air); |
| 132 | 132 | try w.writeAll("("); |
| 133 | 133 | try s.dumpRef(w, call.base); |
src/core/Air.zig+3-2| ... | ... | @@ -16,6 +16,7 @@ pub const Inst = struct { |
| 16 | 16 | block, |
| 17 | 17 | js_global_ref, |
| 18 | 18 | call, |
| 19 | new, | |
| 19 | 20 | @"break", |
| 20 | 21 | @"if", |
| 21 | 22 | arg, |
| ... | ... | @@ -45,7 +46,7 @@ pub const Inst = struct { |
| 45 | 46 | .js_global_ref => InternPool.String.WithLen, |
| 46 | 47 | .@"break" => Break, |
| 47 | 48 | .@"if" => If, |
| 48 | .call => Call, | |
| 49 | .call, .new => Call, | |
| 49 | 50 | .arg => Arg, |
| 50 | 51 | .field => Field, |
| 51 | 52 | .memory => Memory, |
| ... | ... | @@ -258,7 +259,7 @@ pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *InternPool) InternPool.Type { |
| 258 | 259 | |
| 259 | 260 | pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *InternPool) InternPool.Type { |
| 260 | 261 | return switch (air.instTag(index)) { |
| 261 | .js_global_ref => .@"opaque", | |
| 262 | .js_global_ref, .new => .@"opaque", | |
| 262 | 263 | |
| 263 | 264 | .block => { |
| 264 | 265 | const items = air.instData(.block, index).slice(air); |
src/core/Ast.zig+1| ... | ... | @@ -38,6 +38,7 @@ utf8_source: [:0]const u8, |
| 38 | 38 | |
| 39 | 39 | tokens: Token.List.Slice, |
| 40 | 40 | nodes: Node.List.Slice, |
| 41 | /// TODO: Move this to using array.ExtraStore | |
| 41 | 42 | extra: []const u32, |
| 42 | 43 | |
| 43 | 44 | pub const Node = struct { |
src/core/JsLink.zig+44-4| ... | ... | @@ -152,9 +152,22 @@ fn printDecl(link: *JsLink, decl_index: Decl.Index, writer: anytype) !void { |
| 152 | 152 | try link.printLocal(@intCast(id), writer); |
| 153 | 153 | if (id != tree.param_count - 1) try writer.writeAll(", "); |
| 154 | 154 | } |
| 155 | try writer.writeAll(") => {\n"); | |
| 156 | try link.printDeclContents(tree, writer); | |
| 157 | try writer.writeAll("};\n"); | |
| 155 | try writer.writeAll(") => "); | |
| 156 | ||
| 157 | const stmts = tree.mainStatements(); | |
| 158 | if (stmts.len == 1 and tree.nodeTag(stmts[0]) == .stmt_return) { | |
| 159 | if (tree.nodeData(.stmt_return, stmts[0]).operand.unwrap()) |operand| { | |
| 160 | try link.printExpr(tree, operand, writer); | |
| 161 | } else { | |
| 162 | try writer.writeAll("{}"); | |
| 163 | } | |
| 164 | } else { | |
| 165 | try writer.writeAll("{\n"); | |
| 166 | try link.printDeclContents(tree, writer); | |
| 167 | try writer.writeAll("}"); | |
| 168 | } | |
| 169 | ||
| 170 | try writer.writeAll(";\n"); | |
| 158 | 171 | } |
| 159 | 172 | |
| 160 | 173 | fn printDeclContents(link: *JsLink, tree: *const JsTree, writer: anytype) !void { |
| ... | ... | @@ -167,7 +180,12 @@ fn printDeclContents(link: *JsLink, tree: *const JsTree, writer: anytype) !void |
| 167 | 180 | } |
| 168 | 181 | } |
| 169 | 182 | |
| 170 | fn printBlockContents(link: *JsLink, tree: *const JsTree, list: JsTree.IndexList, writer: anytype) !void { | |
| 183 | fn printBlockContents( | |
| 184 | link: *JsLink, | |
| 185 | tree: *const JsTree, | |
| 186 | list: JsTree.IndexList, | |
| 187 | writer: anytype, | |
| 188 | ) !void { | |
| 171 | 189 | var t = tracer.trace(@src()); |
| 172 | 190 | defer t.end(); |
| 173 | 191 | |
| ... | ... | @@ -377,6 +395,28 @@ fn printExprLevel( |
| 377 | 395 | |
| 378 | 396 | try writer.writeAll(")"); |
| 379 | 397 | }, |
| 398 | .new => { | |
| 399 | const call: JsTree.Call = tree.nodeData(.new, node); | |
| 400 | const args = call.args.get(tree); | |
| 401 | ||
| 402 | if (Level.new.needsParens(level)) | |
| 403 | try writer.writeAll("("); | |
| 404 | try writer.writeAll("new "); | |
| 405 | try link.printExpr(tree, call.base, writer); | |
| 406 | ||
| 407 | if (args.len > 0) { | |
| 408 | try writer.writeAll("("); | |
| 409 | ||
| 410 | for (args.slice(tree), 0..) |arg, i| { | |
| 411 | if (i != 0) try writer.writeAll(", "); | |
| 412 | try link.printExpr(tree, arg, writer); | |
| 413 | } | |
| 414 | ||
| 415 | try writer.writeAll(")"); | |
| 416 | } | |
| 417 | if (Level.new.needsParens(level)) | |
| 418 | try writer.writeAll(")"); | |
| 419 | }, | |
| 380 | 420 | |
| 381 | 421 | .field => { |
| 382 | 422 | const field: JsTree.Field = tree.nodeData(.field, node); |
src/core/JsNodeGen.zig+4-3| ... | ... | @@ -260,8 +260,8 @@ fn codeGenInst(c: *JsNodeGen, inst: Air.Inst.Index) Error!void { |
| 260 | 260 | try c.markExpression(inst, node); |
| 261 | 261 | }, |
| 262 | 262 | |
| 263 | .call => { | |
| 264 | const data: Air.Call = c.air.instData(.call, inst); | |
| 263 | inline .call, .new => |tag| { | |
| 264 | const data: Air.Call = c.air.instData(tag, inst); | |
| 265 | 265 | const call_data = data.call_data.get(c.air); |
| 266 | 266 | |
| 267 | 267 | try c.extra.list.ensureUnusedCapacity(c.gpa, call_data.args.len); |
| ... | ... | @@ -274,7 +274,7 @@ fn codeGenInst(c: *JsNodeGen, inst: Air.Inst.Index) Error!void { |
| 274 | 274 | dest.* = @intFromEnum(try c.fetchExpression(arg)); |
| 275 | 275 | } |
| 276 | 276 | |
| 277 | const node = try c.add(.call, .{ | |
| 277 | const node = try c.add(@field(JsTree.Tag, @tagName(tag)), .{ | |
| 278 | 278 | .base = try c.fetchExpression(data.base), |
| 279 | 279 | .args = try c.extra.append(c.gpa, JsTree.IndexList, .{ |
| 280 | 280 | .start = @intCast(start), |
| ... | ... | @@ -603,6 +603,7 @@ fn canLowerToExpression(c: *const JsNodeGen, inst: Air.Inst.Ref) bool { |
| 603 | 603 | .index => |index| switch (c.air.instTag(index)) { |
| 604 | 604 | .js_global_ref, |
| 605 | 605 | .call, |
| 606 | .new, | |
| 606 | 607 | .arg, |
| 607 | 608 | .field, |
| 608 | 609 | .add, |
src/core/JsTree.zig+2-1| ... | ... | @@ -39,6 +39,7 @@ pub const Tag = enum { |
| 39 | 39 | gt, |
| 40 | 40 | // exprs |
| 41 | 41 | call, |
| 42 | new, | |
| 42 | 43 | field, |
| 43 | 44 | ternary, |
| 44 | 45 | // prefix ops |
| ... | ... | @@ -84,7 +85,7 @@ pub const Tag = enum { |
| 84 | 85 | .pre_inc, |
| 85 | 86 | .pre_dec, |
| 86 | 87 | => Un, |
| 87 | .call => Call, | |
| 88 | .call, .new => Call, | |
| 88 | 89 | .field => Field, |
| 89 | 90 | .stmt_if => StmtIf, |
| 90 | 91 | .stmt_break => StmtBreak, |
src/core/Report.zig+7-5| ... | ... | @@ -817,11 +817,10 @@ pub const LazyNode = struct { |
| 817 | 817 | |
| 818 | 818 | switch (teg.instTag(lazy.inst)) { |
| 819 | 819 | .as => switch (node_tag) { |
| 820 | .var_decl => { | |
| 821 | switch (lazy.operand) { | |
| 822 | .rhs => return data.var_decl.init, | |
| 823 | else => {}, | |
| 824 | } | |
| 820 | .var_decl => switch (lazy.operand) { | |
| 821 | .lhs => return data.var_decl.type.unwrap().?, | |
| 822 | .rhs => return data.var_decl.init, | |
| 823 | else => {}, | |
| 825 | 824 | }, |
| 826 | 825 | else => {}, |
| 827 | 826 | }, |
| ... | ... | @@ -865,6 +864,9 @@ pub const LazyNode = struct { |
| 865 | 864 | else => fn_decl.params.slice(ctx.ast)[lazy.operand.getArgId()], |
| 866 | 865 | }; |
| 867 | 866 | }, |
| 867 | .js => { | |
| 868 | return base_node; | |
| 869 | }, | |
| 868 | 870 | else => {}, |
| 869 | 871 | } |
| 870 | 872 |
src/core/Sema.zig+30-3| ... | ... | @@ -425,6 +425,31 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 425 | 425 | }), |
| 426 | 426 | }); |
| 427 | 427 | }, |
| 428 | .js_new => { | |
| 429 | const call: Teg.Inst.JsNew = s.code.instPayload(.js_new, in); | |
| 430 | const base = try s.resolveRefRuntime(call.base); | |
| 431 | ||
| 432 | const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len); | |
| 433 | defer s.arena.free(scratch); | |
| 434 | for (call.args.slice(&s.code), 0..) |arg, i| { | |
| 435 | const ref = try s.ensureValueRuntime( | |
| 436 | in.op(Report.Operand.argument(@intCast(i))), | |
| 437 | try s.resolveRefRuntime(arg), | |
| 438 | ); | |
| 439 | scratch[i] = ref; | |
| 440 | } | |
| 441 | ||
| 442 | const start = s.air_extra.list.items.len; | |
| 443 | try s.air_extra.list.appendSlice(s.gpa, @ptrCast(scratch)); | |
| 444 | ||
| 445 | return s.addInstRef(.new, .{ | |
| 446 | .base = base, | |
| 447 | .call_data = try s.air_extra.append(s.gpa, Air.Call.Data, .{ .args = .{ | |
| 448 | .start = @intCast(start), | |
| 449 | .len = @intCast(scratch.len), | |
| 450 | } }), | |
| 451 | }); | |
| 452 | }, | |
| 428 | 453 | .ensure_void => { |
| 429 | 454 | const operand = try s.resolveRefRuntimeExtra(s.code.instData(.ensure_void, in).operand, false); |
| 430 | 455 | const t = s.typeOfRef(operand); |
| ... | ... | @@ -447,9 +472,8 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { |
| 447 | 472 | .@"if" => { |
| 448 | 473 | const data: Teg.Inst.If = s.code.instData(.@"if", in).extra_index.get(&s.code); |
| 449 | 474 | |
| 450 | const cond = try s.resolveRefRuntime(data.cond); | |
| 451 | ||
| 452 | // try s.validateType(s.typeOfRef(cond), .bool_type); | |
| 475 | const cond_raw = try s.resolveRefRuntime(data.cond); | |
| 476 | const cond = try s.coerceRef(in.op(.operand), cond_raw, .bool); | |
| 453 | 477 | |
| 454 | 478 | // If known at comptime, only analyze the taken path |
| 455 | 479 | if (cond.kind == .value) { |
| ... | ... | @@ -636,6 +660,9 @@ fn coerceRef(s: *Sema, src: InstOp, value: Air.Inst.Ref, to: Type) !Air.Inst.Ref |
| 636 | 660 | if (from == to) { |
| 637 | 661 | return value; |
| 638 | 662 | } |
| 663 | if (from == .@"opaque") { | |
| 664 | return value; | |
| 665 | } | |
| 639 | 666 | const index = switch (value.decode()) { |
| 640 | 667 | .value => |v| return (try s.coerceValue(src, v, to)).toAir(), |
| 641 | 668 | .index => |index| index, |
src/core/Teg.Dump.zig+8| ... | ... | @@ -186,6 +186,14 @@ const State = struct { |
| 186 | 186 | try s.dumpRefMarkerList(writer, call.args.slice(teg), '[', ']'); |
| 187 | 187 | try writer.writeAll(")"); |
| 188 | 188 | }, |
| 189 | .js_new => { | |
| 190 | const call: Teg.Inst.JsNew = teg.instData(.js_new, inst).extra_index.get(teg); | |
| 191 | try writer.writeAll("("); | |
| 192 | try s.dumpRefMarker(writer, call.base); | |
| 193 | try writer.writeAll(","); | |
| 194 | try s.dumpRefMarkerList(writer, call.args.slice(teg), '[', ']'); | |
| 195 | try writer.writeAll(")"); | |
| 196 | }, | |
| 189 | 197 | .load_decl => { |
| 190 | 198 | const call: Teg.Inst.StrNode = teg.instData(.load_decl, inst); |
| 191 | 199 |
src/core/Teg.zig+8| ... | ... | @@ -72,6 +72,8 @@ pub const Inst = struct { |
| 72 | 72 | |
| 73 | 73 | /// Implements the `@js` builtin |
| 74 | 74 | js, |
| 75 | /// Implements the `@jsNew` builtin | |
| 76 | js_new, | |
| 75 | 77 | /// Implements the `@import` builtin |
| 76 | 78 | import, |
| 77 | 79 | /// Implements the `@fetch` builtin |
| ... | ... | @@ -95,6 +97,7 @@ pub const Inst = struct { |
| 95 | 97 | .load_decl => StrNode, |
| 96 | 98 | .param => DataNode, |
| 97 | 99 | .store => PlNode(Store), |
| 100 | .js_new => PlNode(JsNew), | |
| 98 | 101 | |
| 99 | 102 | // Single instruction operand |
| 100 | 103 | .memory, |
| ... | ... | @@ -307,6 +310,11 @@ pub const Inst = struct { |
| 307 | 310 | no: Inst.Ref, |
| 308 | 311 | }; |
| 309 | 312 | |
| 313 | pub const JsNew = struct { | |
| 314 | base: Inst.Ref, | |
| 315 | args: RefList, | |
| 316 | }; | |
| 317 | ||
| 310 | 318 | /// This will contain trailing data depending on the .kind |
| 311 | 319 | pub const Decl = struct { |
| 312 | 320 | name: NullTerminatedString, |
src/core/Tokenizer.zig+42-10| ... | ... | @@ -274,7 +274,7 @@ pub const Token = struct { |
| 274 | 274 | utf8_source[token.offset..][0..token.len(utf8_source)]; |
| 275 | 275 | } |
| 276 | 276 | |
| 277 | const keywords = std.ComptimeStringMap(Tag, keywords: { | |
| 277 | const keywords = std.StaticStringMap(Tag).initComptime(keywords: { | |
| 278 | 278 | const fields = std.enums.values(Tag); |
| 279 | 279 | const KV = struct { []const u8, Tag }; |
| 280 | 280 | var kws: [fields.len]KV = undefined; |
| ... | ... | @@ -355,18 +355,33 @@ pub fn next(t: *Tokenizer) !?Token { |
| 355 | 355 | (t.byteAt(3) orelse 0 != '/')) |
| 356 | 356 | { |
| 357 | 357 | const start = t.pos; |
| 358 | t.pos = @intCast(mem.indexOfScalarPos(u8, t.code, t.pos + 3, '\n') orelse t.code.len); | |
| 358 | t.pos = @intCast(mem.indexOfScalarPos( | |
| 359 | u8, | |
| 360 | t.code, | |
| 361 | t.pos + 3, | |
| 362 | '\n', | |
| 363 | ) orelse t.code.len); | |
| 359 | 364 | return .{ .tag = .doc_comment, .offset = start }; |
| 360 | 365 | } |
| 361 | 366 | |
| 362 | 367 | if (t.byteAt(2) orelse 0 == '!') { |
| 363 | 368 | const start = t.pos; |
| 364 | t.pos = @intCast(mem.indexOfScalarPos(u8, t.code, t.pos + 2, '\n') orelse t.code.len); | |
| 369 | t.pos = @intCast(mem.indexOfScalarPos( | |
| 370 | u8, | |
| 371 | t.code, | |
| 372 | t.pos + 2, | |
| 373 | '\n', | |
| 374 | ) orelse t.code.len); | |
| 365 | 375 | return .{ .tag = .module_doc_comment, .offset = start }; |
| 366 | 376 | } |
| 367 | 377 | |
| 368 | 378 | // skip the comment |
| 369 | t.pos = @intCast(mem.indexOfScalarPos(u8, t.code, t.pos + 2, '\n') orelse t.code.len); | |
| 379 | t.pos = @intCast(mem.indexOfScalarPos( | |
| 380 | u8, | |
| 381 | t.code, | |
| 382 | t.pos + 2, | |
| 383 | '\n', | |
| 384 | ) orelse t.code.len); | |
| 370 | 385 | t.skipWhitespace(); |
| 371 | 386 | continue; |
| 372 | 387 | }, |
| ... | ... | @@ -376,7 +391,12 @@ pub fn next(t: *Tokenizer) !?Token { |
| 376 | 391 | '\\' => switch (t.nextByte()) { |
| 377 | 392 | '\\' => { |
| 378 | 393 | const start = t.pos; |
| 379 | t.pos = @intCast(mem.indexOfScalarPos(u8, t.code, t.pos + 2, '\n') orelse t.code.len); | |
| 394 | t.pos = @intCast(mem.indexOfScalarPos( | |
| 395 | u8, | |
| 396 | t.code, | |
| 397 | t.pos + 2, | |
| 398 | '\n', | |
| 399 | ) orelse t.code.len); | |
| 380 | 400 | return .{ .tag = .multiline_string, .offset = start }; |
| 381 | 401 | }, |
| 382 | 402 | else => return t.maybeUTF8Error(error.InvalidSyntax), |
| ... | ... | @@ -607,8 +627,11 @@ fn tokenizeNumber(t: *Tokenizer) !?Token { |
| 607 | 627 | else => unreachable, |
| 608 | 628 | } |
| 609 | 629 | |
| 630 | const start = t.pos; | |
| 631 | var tag: Token.Tag = .number_int; | |
| 632 | ||
| 610 | 633 | // Parse Hex, Binary, Octal numbers |
| 611 | if (t.byte() == '0') { | |
| 634 | if (t.byte() == '0') alt_base: { | |
| 612 | 635 | switch (t.nextByte()) { |
| 613 | 636 | 'x', 'X' => { |
| 614 | 637 | t.pos += 2; |
| ... | ... | @@ -622,12 +645,21 @@ fn tokenizeNumber(t: *Tokenizer) !?Token { |
| 622 | 645 | t.pos += 2; |
| 623 | 646 | try t.consumeIntegerRadix(.octal); |
| 624 | 647 | }, |
| 625 | else => {}, | |
| 648 | '0' => @panic("TODO: no double zero"), | |
| 649 | else => break :alt_base, | |
| 626 | 650 | } |
| 627 | } | |
| 628 | 651 | |
| 629 | const start = t.pos; | |
| 630 | var tag: Token.Tag = .number_int; | |
| 652 | // after numbers must be a valid word break the above returns are right | |
| 653 | // before parsing `.` so those can be unchecked. | |
| 654 | if (try t.isAtIdentifierStart()) { | |
| 655 | return error.InvalidSyntax; | |
| 656 | } | |
| 657 | ||
| 658 | return .{ | |
| 659 | .tag = tag, | |
| 660 | .offset = start, | |
| 661 | }; | |
| 662 | } | |
| 631 | 663 | |
| 632 | 664 | if (t.byte() == '.') { |
| 633 | 665 | t.pos += 1; |
src/core/Transform.zig+52-26| ... | ... | @@ -216,7 +216,7 @@ const BlockContext = struct { |
| 216 | 216 | |
| 217 | 217 | const Liveness = enum { alive, dead }; |
| 218 | 218 | |
| 219 | const BuiltInIds = std.ComptimeStringMap(Inst.Ref, .{ | |
| 219 | const BuiltInIds = std.StaticStringMap(Inst.Ref).initComptime(.{ | |
| 220 | 220 | .{ "void", .void }, |
| 221 | 221 | .{ "true", .bool_true }, |
| 222 | 222 | .{ "false", .bool_false }, |
| ... | ... | @@ -571,7 +571,7 @@ pub const Builtin = enum { |
| 571 | 571 | compile_error, |
| 572 | 572 | type_of, |
| 573 | 573 | |
| 574 | const map = std.ComptimeStringMap(Builtin, .{ | |
| 574 | const map = std.StaticStringMap(Builtin).initComptime(.{ | |
| 575 | 575 | // sort-lines: start |
| 576 | 576 | .{ "compileError", .compile_error }, |
| 577 | 577 | .{ "fetch", .fetch }, |
| ... | ... | @@ -602,6 +602,30 @@ pub const Builtin = enum { |
| 602 | 602 | }); |
| 603 | 603 | } |
| 604 | 604 | |
| 605 | fn builtin_jsNew(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref { | |
| 606 | const trace = tracer.trace(@src()); | |
| 607 | defer trace.end(); | |
| 608 | ||
| 609 | if (args.len == 0) { | |
| 610 | try t.report.err(.builtin_mismatch_args, .{ .node = node, .args = 1 }); | |
| 611 | return .void; | |
| 612 | } | |
| 613 | ||
| 614 | const base = try t.transformExpression(decl, args[0]) orelse return null; | |
| 615 | const args_list = try t.transformCallArgs(decl, args[1..]) orelse return null; | |
| 616 | ||
| 617 | return try t.addInstRef(.{ | |
| 618 | .tag = .js_new, | |
| 619 | .data = EncodedData.encode(.js_new, .{ | |
| 620 | .src_node = decl.nodeOffset(args[0]), | |
| 621 | .extra_index = try t.extra.append(t.gpa, Inst.JsNew, .{ | |
| 622 | .base = base, | |
| 623 | .args = args_list, | |
| 624 | }), | |
| 625 | }), | |
| 626 | }); | |
| 627 | } | |
| 628 | ||
| 605 | 629 | fn builtin_compileError(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref { |
| 606 | 630 | const trace = tracer.trace(@src()); |
| 607 | 631 | defer trace.end(); |
| ... | ... | @@ -616,7 +640,7 @@ pub const Builtin = enum { |
| 616 | 640 | |
| 617 | 641 | return try t.addInstRef(.{ |
| 618 | 642 | .tag = .compile_error, |
| 619 | .data = EncodedData.encode(.js, .{ | |
| 643 | .data = EncodedData.encode(.compile_error, .{ | |
| 620 | 644 | .src_node = decl.nodeOffset(node), |
| 621 | 645 | .operand = source, |
| 622 | 646 | }), |
| ... | ... | @@ -637,7 +661,7 @@ pub const Builtin = enum { |
| 637 | 661 | |
| 638 | 662 | return try t.addInstRef(.{ |
| 639 | 663 | .tag = .type_of, |
| 640 | .data = EncodedData.encode(.js, .{ | |
| 664 | .data = EncodedData.encode(.type_of, .{ | |
| 641 | 665 | .src_node = decl.nodeOffset(node), |
| 642 | 666 | .operand = source, |
| 643 | 667 | }), |
| ... | ... | @@ -932,6 +956,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc |
| 932 | 956 | |
| 933 | 957 | return switch (builtin) { |
| 934 | 958 | .js => Builtin.builtin_js(t, decl, node, args), |
| 959 | .js_new => Builtin.builtin_jsNew(t, decl, node, args), | |
| 935 | 960 | // .import => Builtin.import(t, decl, node, args), |
| 936 | 961 | // .fetch => Builtin.fetch(t, decl, node, args), |
| 937 | 962 | .compile_error => Builtin.builtin_compileError(t, decl, node, args), |
| ... | ... | @@ -945,28 +970,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc |
| 945 | 970 | const args = t.ast.extraData(call.extra, Ast.Call).args.slice(&t.ast); |
| 946 | 971 | |
| 947 | 972 | const base = try t.transformExpression(decl, call.base) orelse return null; |
| 948 | ||
| 949 | const args_list: RefList = brk: { | |
| 950 | const args_top = t.call_args.items.len; | |
| 951 | defer t.call_args.shrinkRetainingCapacity(args_top); | |
| 952 | ||
| 953 | for (args) |arg| { | |
| 954 | try t.call_args.append( | |
| 955 | t.gpa, | |
| 956 | try t.transformExpression(decl, arg) orelse | |
| 957 | return null, | |
| 958 | ); | |
| 959 | } | |
| 960 | ||
| 961 | const items = t.call_args.items[args_top..]; | |
| 962 | const extra_top = t.extra.list.items.len; | |
| 963 | try t.extra.list.appendSlice(t.gpa, @ptrCast(items)); | |
| 964 | ||
| 965 | break :brk .{ | |
| 966 | .start = @intCast(extra_top), | |
| 967 | .len = @intCast(items.len), | |
| 968 | }; | |
| 969 | }; | |
| 973 | const args_list = try t.transformCallArgs(decl, args) orelse return null; | |
| 970 | 974 | |
| 971 | 975 | const call_inst = try t.addInstRef(.{ |
| 972 | 976 | .tag = .call, |
| ... | ... | @@ -1138,6 +1142,28 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc |
| 1138 | 1142 | } |
| 1139 | 1143 | } |
| 1140 | 1144 | |
| 1145 | fn transformCallArgs(t: *Transform, decl: BlockContext, args: []const NodeIndex) !?RefList { | |
| 1146 | const args_top = t.call_args.items.len; | |
| 1147 | defer t.call_args.shrinkRetainingCapacity(args_top); | |
| 1148 | ||
| 1149 | for (args) |arg| { | |
| 1150 | try t.call_args.append( | |
| 1151 | t.gpa, | |
| 1152 | try t.transformExpression(decl, arg) orelse | |
| 1153 | return null, | |
| 1154 | ); | |
| 1155 | } | |
| 1156 | ||
| 1157 | const items = t.call_args.items[args_top..]; | |
| 1158 | const extra_top = t.extra.list.items.len; | |
| 1159 | try t.extra.list.appendSlice(t.gpa, @ptrCast(items)); | |
| 1160 | ||
| 1161 | return .{ | |
| 1162 | .start = @intCast(extra_top), | |
| 1163 | .len = @intCast(items.len), | |
| 1164 | }; | |
| 1165 | } | |
| 1166 | ||
| 1141 | 1167 | fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex) Allocator.Error!?Inst.Index { |
| 1142 | 1168 | const node_tag = t.ast.nodes.items(.tag)[node]; |
| 1143 | 1169 |
src/langref/generate-langref.zig+6-1| ... | ... | @@ -182,6 +182,8 @@ fn processMarkdown(gpa: std.mem.Allocator, src: []const u8, compiler_path: []con |
| 182 | 182 | try writeTokenizedCode(gpa, code.items, writer); |
| 183 | 183 | try writer.writeAll("</code></pre>"); |
| 184 | 184 | |
| 185 | try code.append('\n'); | |
| 186 | ||
| 185 | 187 | switch (tag) { |
| 186 | 188 | .code_run, |
| 187 | 189 | .code_build, |
| ... | ... | @@ -208,7 +210,8 @@ fn processMarkdown(gpa: std.mem.Allocator, src: []const u8, compiler_path: []con |
| 208 | 210 | } |
| 209 | 211 | if (std.mem.trim(u8, out.items, " \n\r").len > 0) { |
| 210 | 212 | try writer.writeAll("<pre><code>"); |
| 211 | try writer.writeAll(out.items); | |
| 213 | try writer.writeAll("// Compiled JavaScript Code"); | |
| 214 | try writer.writeAll(out.items[std.mem.indexOfScalar(u8, out.items, '\n').?..]); | |
| 212 | 215 | try writer.writeAll("</code></pre>"); |
| 213 | 216 | } |
| 214 | 217 | }, |
| ... | ... | @@ -302,6 +305,8 @@ fn processInlineMarkdown(gpa: std.mem.Allocator, src: []const u8, writer: anytyp |
| 302 | 305 | } |
| 303 | 306 | } |
| 304 | 307 | |
| 308 | // TODO: Change this to use Tree-sitter, which would also make it very simple | |
| 309 | // to highlight JavaScript code in addition to the language. | |
| 305 | 310 | fn writeTokenizedCode(gpa: std.mem.Allocator, code: []const u8, writer: anytype) !void { |
| 306 | 311 | const code_z = try gpa.dupeZ(u8, code); |
| 307 | 312 | defer gpa.free(code_z); |
src/langref/template.html+36-14| ... | ... | @@ -8,7 +8,7 @@ |
| 8 | 8 | * { margin: 0; padding: 0; box-sizing: border-box; } |
| 9 | 9 | body { |
| 10 | 10 | background-color: #fefefe; |
| 11 | font-family: sans-serif; | |
| 11 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; | |
| 12 | 12 | max-width: 45rem; |
| 13 | 13 | padding: 1rem; |
| 14 | 14 | margin: auto; |
| ... | ... | @@ -29,44 +29,66 @@ |
| 29 | 29 | padding-top: 8px; |
| 30 | 30 | padding-bottom: 8px; |
| 31 | 31 | } |
| 32 | pre { | |
| 33 | padding: 0.5rem; | |
| 34 | } | |
| 35 | 32 | pre > code { |
| 36 | 33 | border: none; |
| 37 | 34 | padding: 0; |
| 38 | 35 | background-color: transparent; |
| 39 | 36 | } |
| 40 | 37 | code, pre { |
| 41 | border: 1px solid #999; | |
| 42 | background-color: #eeeeee; | |
| 38 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | |
| 39 | font-size: 0.9rem; | |
| 40 | border: 1px solid #9ca0b0; | |
| 41 | background-color: #eff1f5; | |
| 43 | 42 | border-radius: 4px; |
| 44 | 43 | padding: 2px 4px; |
| 45 | 44 | } |
| 45 | pre { | |
| 46 | padding: 0.75rem 0.75rem; | |
| 47 | } | |
| 46 | 48 | .term { |
| 47 | border: 1px solid #333; | |
| 48 | background-color: #222; | |
| 49 | color:white; | |
| 49 | border: 1px solid #11111b; | |
| 50 | background-color: #24273a; | |
| 51 | color: #cdd6f4; | |
| 50 | 52 | } |
| 53 | ||
| 51 | 54 | .ansi-1 { |
| 52 | 55 | font-weight: bold; |
| 53 | 56 | } |
| 54 | 57 | .ansi-31 { |
| 55 | color:rgb(255, 68, 68); | |
| 58 | color: #ed8796; | |
| 59 | } | |
| 60 | .ansi-32 { | |
| 61 | color: #a6e3a1; | |
| 62 | } | |
| 63 | .ansi-33 { | |
| 64 | color: #eed49f; | |
| 65 | } | |
| 66 | .ansi-34 { | |
| 67 | color: #8aadf4; | |
| 68 | } | |
| 69 | .ansi-35 { | |
| 70 | color: #f5c2e7; | |
| 71 | } | |
| 72 | .ansi-36 { | |
| 73 | color: #94e2d5; | |
| 56 | 74 | } |
| 57 | 75 | .ansi-2 { |
| 58 | color:#aaa; | |
| 76 | color: #8087a2; | |
| 59 | 77 | } |
| 60 | 78 | |
| 61 | 79 | .kw { |
| 62 | color: mediumpurple; | |
| 80 | color: #8839ef; | |
| 63 | 81 | font-weight: bold; |
| 64 | 82 | } |
| 65 | 83 | .tok-string { |
| 66 | color: green; | |
| 84 | color: #40a02b; | |
| 85 | } | |
| 86 | .tok-builtin_id { | |
| 87 | color: #d20f39; | |
| 88 | font-weight: bold; | |
| 67 | 89 | } |
| 68 | 90 | .tok-number_int, .tok-number_float { |
| 69 | color: orangered; | |
| 91 | color: #fe640b; | |
| 70 | 92 | } |
| 71 | 93 | </style> |
| 72 | 94 | </head> |