├── .gitignore ├── LICENSE ├── README.md ├── build.zig └── src └── main.zig /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | zig-out/ 3 | src/zig-cache/ 4 | example 5 | .idea 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Yasuhiro Matsumoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-lisp 2 | 3 | Toy implementation of Lisp written in Zig. 4 | 5 | ## Usage 6 | 7 | ``` 8 | $ zig run -- file.lisp 9 | ``` 10 | 11 | ## Requirements 12 | 13 | * Zig 14 | 15 | ## Build 16 | 17 | ``` 18 | $ zig build 19 | ``` 20 | 21 | ## License 22 | 23 | MIT 24 | 25 | ## Author 26 | 27 | Yasuhiro Matsumoto (a.k.a. mattn) 28 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) void { 4 | // Standard target options allows the person running `zig build` to choose 5 | // what target to build for. Here we do not override the defaults, which 6 | // means any target is allowed, and the default is native. Other options 7 | // for restricting supported target set are available. 8 | const target = b.standardTargetOptions(.{}); 9 | 10 | // Standard release options allow the person running `zig build` to select 11 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. 12 | const optimize = b.standardOptimizeOption(.{}); 13 | 14 | const exe = b.addExecutable(.{ 15 | .name = "zig-lisp", 16 | .root_source_file = .{ .path = "src/main.zig" }, 17 | .target = target, 18 | .optimize = optimize, 19 | }); 20 | b.installArtifact(exe); 21 | 22 | const run_cmd = b.addRunArtifact(exe); 23 | run_cmd.step.dependOn(b.getInstallStep()); 24 | if (b.args) |args| { 25 | run_cmd.addArgs(args); 26 | } 27 | 28 | const run_step = b.step("run", "Run the app"); 29 | run_step.dependOn(&run_cmd.step); 30 | 31 | const exe_tests = b.addTest(.{ 32 | .name = "zig-lisp", 33 | .root_source_file = .{ .path = "src/main.zig" }, 34 | .target = target, 35 | .optimize = optimize, 36 | }); 37 | 38 | const test_step = b.step("test", "Run unit tests"); 39 | test_step.dependOn(&exe_tests.step); 40 | } 41 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const cell = struct { 4 | car: ?*atom, 5 | cdr: ?*atom, 6 | }; 7 | 8 | const lambda = struct { 9 | e: ?*env, 10 | cell: cell, 11 | }; 12 | 13 | const ref = ?*atom; 14 | 15 | const function = struct { 16 | name: []const u8, 17 | ptr: *const fn (*env, std.mem.Allocator, *atom) LispError!*atom, 18 | }; 19 | 20 | const env = struct { 21 | a: std.mem.Allocator, 22 | v: std.StringArrayHashMap(*atom), 23 | p: ?*env, 24 | err: ?[]const u8, 25 | 26 | const Self = @This(); 27 | 28 | pub fn init(a: std.mem.Allocator) Self { 29 | return Self{ 30 | .a = a, 31 | .v = std.StringArrayHashMap(*atom).init(a), 32 | .p = null, 33 | .err = null, 34 | }; 35 | } 36 | 37 | pub fn get(self: *Self, key: []const u8) !?*atom { 38 | var e: *env = self; 39 | while (true) { 40 | if (e.v.get(key)) |ev| { 41 | return ev; 42 | } 43 | if (e.p == null) { 44 | break; 45 | } 46 | e = e.p.?; 47 | } 48 | try e.raise("invalid symbol"); 49 | unreachable; 50 | } 51 | 52 | pub fn child(self: *Self) Self { 53 | const c = Self{ 54 | .a = self.a, 55 | .v = std.StringArrayHashMap(*atom).init(self.a), 56 | .p = self, 57 | .err = null, 58 | }; 59 | return c; 60 | } 61 | 62 | pub fn deinit(self: *Self) void { 63 | self.v.clearAndFree(); 64 | self.v.deinit(); 65 | if (self.err != null) { 66 | self.a.free(self.err.?); 67 | } 68 | } 69 | 70 | pub fn raise(self: *Self, msg: []const u8) LispError!void { 71 | self.err = try self.a.dupe(u8, msg); 72 | return error.RuntimeError; 73 | } 74 | 75 | pub fn printerr(self: *Self, err: anyerror) !void { 76 | if (self.err != null) { 77 | try std.io.getStdErr().writer().print("{}: {s}\n", .{ err, self.err.? }); 78 | self.err = null; 79 | } else { 80 | try std.io.getStdErr().writer().print("{}\n", .{err}); 81 | } 82 | } 83 | }; 84 | 85 | const atom = union(enum) { 86 | sym: std.ArrayList(u8), 87 | bool: bool, 88 | num: i64, 89 | str: std.ArrayList(u8), 90 | lambda: lambda, 91 | func: *const function, 92 | quote: ?*atom, 93 | cell: cell, 94 | none: ?void, 95 | 96 | const Self = @This(); 97 | 98 | pub fn init(a: std.mem.Allocator) !*atom { 99 | return try a.create(atom); 100 | } 101 | 102 | pub fn copy(self: *Self, a: std.mem.Allocator) !*Self { 103 | const n = try atom.init(a); 104 | n.* = self.*; 105 | return n; 106 | } 107 | 108 | pub fn deinit(self: *Self, a: std.mem.Allocator, final: bool) void { 109 | switch (self.*) { 110 | .sym => |v| v.deinit(), 111 | .str => |v| v.deinit(), 112 | .lambda => |v| { 113 | if (!final) { 114 | return; 115 | } 116 | if (v.cell.car != null) { 117 | v.cell.car.?.deinit(a, final); 118 | self.cell.car = null; 119 | } 120 | if (v.cell.cdr != null) { 121 | v.cell.cdr.?.deinit(a, final); 122 | self.cell.cdr = null; 123 | } 124 | }, 125 | .cell => |v| { 126 | if (!final) { 127 | return; 128 | } 129 | if (v.car != null) { 130 | v.car.?.deinit(a, final); 131 | self.cell.car = null; 132 | } 133 | if (v.cdr != null) { 134 | v.cdr.?.deinit(a, final); 135 | self.cell.cdr = null; 136 | } 137 | }, 138 | .quote => |v| { 139 | if (final) { 140 | v.?.deinit(a, true); 141 | } 142 | }, 143 | .bool => {}, 144 | .num => {}, 145 | .func => {}, 146 | .none => {}, 147 | } 148 | a.destroy(self); 149 | } 150 | 151 | pub fn println(self: @This(), w: anytype, quoted: bool) LispError!void { 152 | try self.print(w, quoted); 153 | try w.writeByte('\n'); 154 | } 155 | 156 | pub fn print(self: @This(), w: anytype, quoted: bool) LispError!void { 157 | try w.writeByte('\n'); 158 | try self.princ(w, quoted); 159 | } 160 | 161 | pub fn princ(self: @This(), w: anytype, quoted: bool) LispError!void { 162 | switch (self) { 163 | .none => try w.writeAll("null"), 164 | .sym => |v| try w.writeAll(v.items), 165 | .str => |v| { 166 | if (quoted) { 167 | try w.writeByte('"'); 168 | for (v.items) |c| { 169 | switch (c) { 170 | '\\' => try w.writeAll("\\\\"), 171 | '"' => try w.writeAll("\\\""), 172 | '\n' => try w.writeAll("\\n"), 173 | '\r' => try w.writeAll("\\r"), 174 | else => try w.writeByte(c), 175 | } 176 | } 177 | try w.writeByte('"'); 178 | } else { 179 | try w.writeAll(v.items); 180 | } 181 | }, 182 | .func => |v| try w.writeAll(v.name), 183 | .bool => |v| { 184 | if (v) { 185 | try w.writeAll("T"); 186 | } else { 187 | try w.writeAll("nil"); 188 | } 189 | }, 190 | .num => |v| try w.print("{}", .{v}), 191 | .lambda => |v| { 192 | try w.writeAll("(lambda "); 193 | try v.cell.cdr.?.cell.car.?.cell.cdr.?.princ(w, quoted); 194 | try w.writeByte(' '); 195 | try v.cell.cdr.?.cell.car.?.princ(w, quoted); 196 | try w.writeByte(')'); 197 | }, 198 | .cell => |v| { 199 | try w.writeByte('('); 200 | try v.car.?.princ(w, false); 201 | try w.writeByte(' '); 202 | if (v.cdr == null) { 203 | return; 204 | } 205 | var a = v.cdr; 206 | while (a != null) { 207 | if (a.?.cell.car == null) 208 | break; 209 | try a.?.cell.car.?.princ(w, quoted); 210 | if (a.?.cell.cdr == null) { 211 | break; 212 | } 213 | a = a.?.cell.cdr; 214 | if (a == null) { 215 | break; 216 | } 217 | try w.writeByte(' '); 218 | } 219 | try w.writeByte(')'); 220 | }, 221 | .quote => |v| { 222 | try w.writeByte('\x27'); 223 | try v.?.princ(w, quoted); 224 | }, 225 | } 226 | } 227 | }; 228 | 229 | fn debug(arg: *atom) !void { 230 | try arg.println(std.io.getStdOut().writer(), false); 231 | } 232 | 233 | fn eval(e: *env, a: std.mem.Allocator, root: *atom) LispError!*atom { 234 | var arg: ?*atom = root; 235 | 236 | return switch (arg.?.*) { 237 | atom.sym => |v| blk: { 238 | var p = e; 239 | while (true) { 240 | if (p.v.get(v.items)) |ev| { 241 | break :blk try eval(e, a, ev); 242 | } 243 | if (p.p == null) { 244 | break; 245 | } 246 | p = p.p.?; 247 | } 248 | try e.raise("invalid symbol"); 249 | break :blk root; 250 | }, 251 | atom.str => |v| blk: { 252 | var bytes = std.ArrayList(u8).init(a); 253 | try bytes.writer().writeAll(v.items); 254 | const na = try atom.init(a); 255 | na.* = atom{ 256 | .str = bytes, 257 | }; 258 | break :blk na; 259 | }, 260 | atom.lambda => try arg.?.copy(a), 261 | atom.cell => blk: { 262 | var last = arg.?; 263 | while (true) { 264 | last = try switch (arg.?.cell.car.?.*) { 265 | atom.func => (arg.?.cell.car.?.func.ptr)(e, a, arg.?.cell.cdr.?), 266 | atom.lambda => { 267 | var newe = e.child(); 268 | defer newe.deinit(); 269 | newe.p = arg.?.cell.car.?.lambda.e.?; 270 | var pa = arg.?.cell.car.?.lambda.cell.car; 271 | var fa = arg.?.cell.cdr; 272 | while (pa != null) { 273 | try newe.v.put( 274 | pa.?.cell.car.?.sym.items, 275 | try eval(e, a, fa.?.cell.car.?), 276 | ); 277 | pa = pa.?.cell.cdr; 278 | fa = fa.?.cell.cdr; 279 | } 280 | break :blk eval(&newe, a, arg.?.cell.car.?.lambda.cell.cdr.?); 281 | }, 282 | atom.sym => { 283 | const funcname = arg.?.cell.car.?.sym.items; 284 | for (builtins, 0..) |b, i| { 285 | if (std.mem.eql(u8, b.name, funcname)) { 286 | break :blk (builtins[i].ptr)(e, a, arg.?.cell.cdr.?); 287 | } 288 | } 289 | if (try e.get(funcname)) |f| { 290 | if (f.cell.cdr.?.* == atom.cell) { 291 | var newe = e.child(); 292 | defer newe.deinit(); 293 | newe.p = e; 294 | if (f.cell.cdr != null) { 295 | var pa = f.cell.cdr.?.cell.car; 296 | var fa = arg.?.cell.cdr; 297 | while (pa != null and fa != null) { 298 | try newe.v.put( 299 | pa.?.cell.car.?.sym.items, 300 | try eval(e, a, fa.?.cell.car.?), 301 | ); 302 | pa = pa.?.cell.cdr; 303 | fa = fa.?.cell.cdr; 304 | } 305 | } 306 | break :blk eval(&newe, a, f.cell.cdr.?.cell.cdr.?.cell.car.?); 307 | } 308 | } 309 | try e.raise("invalid function"); 310 | break :blk undefined; 311 | }, 312 | else => eval(e, a, arg.?.cell.car.?), 313 | }; 314 | arg = arg.?.cell.cdr; 315 | if (arg == null) { 316 | break :blk last; 317 | } 318 | } 319 | unreachable; 320 | }, 321 | atom.quote => |v| eval(e, a, v.?), 322 | atom.bool => try arg.?.copy(a), 323 | atom.num => try arg.?.copy(a), 324 | atom.func => try arg.?.copy(a), 325 | atom.none => try arg.?.copy(a), 326 | }; 327 | } 328 | 329 | pub fn do_add(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 330 | var arg = args; 331 | var num: i64 = 0; 332 | while (true) { 333 | var val = try eval(e, a, arg.cell.car.?); 334 | defer val.deinit(a, false); 335 | if (val.* == atom.num) { 336 | num += val.num; 337 | } else { 338 | try e.raise("invalid type for +"); 339 | } 340 | if (arg.cell.cdr == null) { 341 | const na = try atom.init(a); 342 | na.* = atom{ 343 | .num = num, 344 | }; 345 | return na; 346 | } 347 | arg = arg.cell.cdr.?; 348 | } 349 | unreachable; 350 | } 351 | 352 | pub fn do_sub(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 353 | var arg = args; 354 | var val = try eval(e, a, arg.cell.car.?); 355 | if (val.* != atom.num) { 356 | try e.raise("invalid type for -"); 357 | } 358 | var num: i64 = val.num; 359 | val.deinit(a, false); 360 | if (arg.cell.cdr == null) { 361 | const na = try atom.init(a); 362 | na.* = atom{ 363 | .num = num, 364 | }; 365 | return na; 366 | } 367 | while (true) { 368 | arg = arg.cell.cdr.?; 369 | val = try eval(e, a, arg.cell.car.?); 370 | defer val.deinit(a, false); 371 | if (val.* == atom.num) { 372 | num -= val.num; 373 | } else { 374 | try e.raise("invalid type for -"); 375 | } 376 | if (arg.cell.cdr == null) { 377 | const na = try atom.init(a); 378 | na.* = atom{ 379 | .num = num, 380 | }; 381 | return na; 382 | } 383 | } 384 | unreachable; 385 | } 386 | 387 | pub fn do_mat(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 388 | var arg = args; 389 | var num: i64 = 1; 390 | while (true) { 391 | var val = try eval(e, a, arg.cell.car.?); 392 | defer val.deinit(a, false); 393 | if (val.* == atom.num) { 394 | num *= val.num; 395 | } else { 396 | try e.raise("invalid type for *"); 397 | } 398 | if (arg.cell.cdr == null) { 399 | const na = try atom.init(a); 400 | na.* = atom{ 401 | .num = num, 402 | }; 403 | return na; 404 | } 405 | arg = arg.cell.cdr.?; 406 | } 407 | unreachable; 408 | } 409 | 410 | pub fn do_mul(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 411 | var arg = args; 412 | var val = try eval(e, a, arg.cell.car.?); 413 | defer val.deinit(a, false); 414 | if (val.* != atom.num) { 415 | try e.raise("invalid type for /"); 416 | } 417 | var num: i64 = val.num; 418 | if (arg.cell.cdr == null) { 419 | const na = try atom.init(a); 420 | na.* = atom{ 421 | .num = num, 422 | }; 423 | return na; 424 | } 425 | while (true) { 426 | arg = arg.cell.cdr.?; 427 | val = try eval(e, a, arg.cell.car.?); 428 | if (val.* == atom.num) { 429 | num = @divTrunc(num, val.num); 430 | } else { 431 | try e.raise("invalid type for /"); 432 | } 433 | val.deinit(a, false); 434 | if (arg.cell.cdr == null) { 435 | const na = try atom.init(a); 436 | na.* = atom{ 437 | .num = num, 438 | }; 439 | return na; 440 | } 441 | } 442 | unreachable; 443 | } 444 | 445 | pub fn do_lt(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 446 | var arg = args; 447 | var lhs = try eval(e, a, arg.cell.car.?); 448 | defer lhs.deinit(a, false); 449 | if (lhs.* != atom.num) { 450 | try e.raise("invalid type for <"); 451 | } 452 | arg = arg.cell.cdr.?; 453 | var rhs = try eval(e, a, arg.cell.car.?); 454 | defer rhs.deinit(a, false); 455 | if (rhs.* != atom.num) { 456 | try e.raise("invalid type for <"); 457 | } 458 | const na = try atom.init(a); 459 | na.* = atom{ 460 | .bool = lhs.num < rhs.num, 461 | }; 462 | return na; 463 | } 464 | 465 | pub fn do_gt(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 466 | var arg = args; 467 | var lhs = try eval(e, a, arg.cell.car.?); 468 | defer lhs.deinit(a, false); 469 | if (lhs.* != atom.num) { 470 | try e.raise("invalid type for >"); 471 | } 472 | arg = arg.cell.cdr.?; 473 | var rhs = try eval(e, a, arg.cell.car.?); 474 | defer rhs.deinit(a, false); 475 | if (rhs.* != atom.num) { 476 | try e.raise("invalid type for >"); 477 | } 478 | const na = try atom.init(a); 479 | na.* = atom{ 480 | .bool = lhs.num > rhs.num, 481 | }; 482 | return na; 483 | } 484 | 485 | pub fn do_eq(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 486 | var arg = args; 487 | var lhs = try eval(e, a, arg.cell.car.?); 488 | defer lhs.deinit(a, false); 489 | if (lhs.* != atom.num) { 490 | try e.raise("invalid type for ="); 491 | } 492 | arg = arg.cell.cdr.?; 493 | var rhs = try eval(e, a, arg.cell.car.?); 494 | defer rhs.deinit(a, false); 495 | if (rhs.* != atom.num) { 496 | try e.raise("invalid type for ="); 497 | } 498 | const na = try atom.init(a); 499 | na.* = atom{ 500 | .bool = lhs.num == rhs.num, 501 | }; 502 | return na; 503 | } 504 | 505 | pub fn do_mod(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 506 | var arg = args; 507 | var lhs = try eval(e, a, arg.cell.car.?); 508 | defer lhs.deinit(a, false); 509 | if (lhs.* != atom.num) { 510 | try e.raise("invalid type for mod"); 511 | } 512 | arg = arg.cell.cdr.?; 513 | var rhs = try eval(e, a, arg.cell.car.?); 514 | defer rhs.deinit(a, false); 515 | if (rhs.* != atom.num) { 516 | try e.raise("invalid type for mod"); 517 | } 518 | const na = try atom.init(a); 519 | na.* = atom{ 520 | .num = @mod(lhs.num, rhs.num), 521 | }; 522 | return na; 523 | } 524 | 525 | pub fn do_cond(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 526 | var arg = args; 527 | 528 | while (true) { 529 | var cond = try eval(e, a, arg.cell.car.?.cell.car.?); 530 | if (cond.* != atom.bool) { 531 | try e.raise("invalid type for cond"); 532 | } 533 | defer cond.deinit(a, false); 534 | 535 | if (cond.bool) { 536 | return try eval(e, a, arg.cell.car.?.cell.cdr.?.cell.car.?); 537 | } 538 | arg = arg.cell.cdr.?; 539 | } 540 | const na = try atom.init(a); 541 | na.* = atom{ 542 | .bool = false, 543 | }; 544 | return na; 545 | } 546 | 547 | pub fn do_dotimes(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 548 | const arg = args; 549 | 550 | const name = arg.cell.car.?.cell.car; 551 | const count = try eval(e, a, arg.cell.car.?.cell.cdr.?); 552 | if (count.* != atom.num) { 553 | try e.raise("invalid type for dotimes"); 554 | } 555 | var newe = e.child(); 556 | defer newe.deinit(); 557 | 558 | var i: u32 = 0; 559 | while (i < count.num) : (i += 1) { 560 | var nv = try atom.init(a); 561 | defer nv.deinit(a, false); 562 | nv.* = atom{ 563 | .num = i, 564 | }; 565 | try newe.v.put(name.?.sym.items, nv); 566 | var value = try eval(&newe, a, arg.cell.cdr.?.cell.car.?); 567 | defer value.deinit(a, false); 568 | } 569 | const na = try atom.init(a); 570 | na.* = atom{ 571 | .bool = false, 572 | }; 573 | return na; 574 | } 575 | 576 | pub fn do_if(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 577 | var arg = args; 578 | var cond = try eval(e, a, arg.cell.car.?); 579 | defer cond.deinit(a, false); 580 | if (cond.* != atom.bool) { 581 | try e.raise("invalid type for if"); 582 | } 583 | 584 | arg = arg.cell.cdr.?; 585 | if (cond.bool) { 586 | return try eval(e, a, arg.cell.car.?); 587 | } 588 | arg = arg.cell.cdr.?; 589 | return try eval(e, a, arg.cell.car.?); 590 | } 591 | 592 | pub fn do_setq(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 593 | const arg = args; 594 | const val = try eval(e, a, arg.cell.cdr.?.cell.car.?); 595 | const name = arg.cell.car.?; 596 | try e.v.put(name.sym.items, val); 597 | return val; 598 | } 599 | 600 | pub fn do_defun(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 601 | const name = args.cell.car.?; 602 | try e.v.put(name.sym.items, args); 603 | var bytes = std.ArrayList(u8).init(a); 604 | try bytes.writer().writeAll(name.sym.items); 605 | const p = try atom.init(a); 606 | p.* = atom{ 607 | .sym = bytes, 608 | }; 609 | return p; 610 | } 611 | 612 | pub fn do_princ(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 613 | var result = try eval(e, a, args); 614 | try result.princ(std.io.getStdOut().writer(), false); 615 | return result; 616 | } 617 | 618 | pub fn do_print(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 619 | var result = try eval(e, a, args); 620 | try result.print(std.io.getStdOut().writer(), false); 621 | return result; 622 | } 623 | 624 | pub fn do_concatenate(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 625 | var arg = args; 626 | var bytes = std.ArrayList(u8).init(a); 627 | while (true) { 628 | var val = try eval(e, a, arg.cell.car.?); 629 | defer val.deinit(a, false); 630 | if (val.* == atom.str) { 631 | try bytes.writer().writeAll(val.str.items); 632 | } else { 633 | try e.raise("invalid type for concatenate"); 634 | } 635 | if (arg.cell.cdr == null) { 636 | const na = try atom.init(a); 637 | na.* = atom{ 638 | .str = bytes, 639 | }; 640 | return na; 641 | } 642 | arg = arg.cell.cdr.?; 643 | } 644 | unreachable; 645 | } 646 | 647 | pub fn do_funcall(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 648 | const l = try eval(e, a, args.cell.car.?); 649 | var p = try atom.init(a); 650 | p.* = atom{ 651 | .cell = cell{ 652 | .car = l, 653 | .cdr = args.cell.cdr, 654 | }, 655 | }; 656 | defer p.deinit(a, false); 657 | return try eval(e, a, p); 658 | } 659 | 660 | pub fn do_lambda(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 661 | const p = try atom.init(a); 662 | p.* = atom{ 663 | .lambda = .{ 664 | .e = e, 665 | .cell = args.cell, 666 | }, 667 | }; 668 | return p; 669 | } 670 | 671 | pub fn do_length(e: *env, a: std.mem.Allocator, args: *atom) LispError!*atom { 672 | var arg = try eval(e, a, args); 673 | defer arg.deinit(a, false); 674 | var n: i64 = 0; 675 | while (true) { 676 | n += 1; 677 | if (arg.cell.cdr == null) { 678 | const na = try atom.init(a); 679 | na.* = atom{ 680 | .num = n, 681 | }; 682 | return na; 683 | } 684 | arg = arg.cell.cdr.?; 685 | } 686 | unreachable; 687 | } 688 | 689 | var builtins = [_]function{ 690 | .{ .name = "+", .ptr = &do_add }, 691 | .{ .name = "-", .ptr = &do_sub }, 692 | .{ .name = "*", .ptr = &do_mat }, 693 | .{ .name = "/", .ptr = &do_mul }, 694 | .{ .name = "<", .ptr = &do_lt }, 695 | .{ .name = ">", .ptr = &do_gt }, 696 | .{ .name = "=", .ptr = &do_eq }, 697 | .{ .name = "mod", .ptr = &do_mod }, 698 | .{ .name = "cond", .ptr = &do_cond }, 699 | .{ .name = "dotimes", .ptr = &do_dotimes }, 700 | .{ .name = "if", .ptr = &do_if }, 701 | .{ .name = "princ", .ptr = &do_princ }, 702 | .{ .name = "print", .ptr = &do_print }, 703 | .{ .name = "setq", .ptr = &do_setq }, 704 | .{ .name = "defun", .ptr = &do_defun }, 705 | .{ .name = "length", .ptr = &do_length }, 706 | .{ .name = "lambda", .ptr = &do_lambda }, 707 | .{ .name = "funcall", .ptr = &do_funcall }, 708 | .{ .name = "concatenate", .ptr = &do_concatenate }, 709 | }; 710 | 711 | const SyntaxError = error{}; 712 | const RuntimeError = error{}; 713 | const ParseIntError = std.fmt.ParseIntError; 714 | const WriteError = std.os.WriteError; 715 | const LispError = error{ RuntimeError, SyntaxError, OutOfMemory, EndOfStream, NoError, InvalidCharacter, IsDir, ConnectionTimedOut, NotOpenForReading, SocketNotConnected, NetNameDeleted } || ParseIntError || WriteError; 716 | 717 | fn skipWhilte(br: anytype) LispError!void { 718 | const r = br.reader(); 719 | loop: while (true) { 720 | switch (r.readByte() catch 0) { 721 | ' ', '\t', '\r', '\n' => {}, 722 | else => |v| { 723 | if (v != 0) try br.putBackByte(v); 724 | break :loop; 725 | }, 726 | } 727 | } 728 | } 729 | 730 | fn parseString(a: std.mem.Allocator, br: anytype) LispError!*atom { 731 | const r = br.reader(); 732 | var byte = try r.readByte(); 733 | if (byte != '"') return error.SyntaxError; 734 | var bytes = std.ArrayList(u8).init(a); 735 | errdefer bytes.deinit(); 736 | while (true) { 737 | byte = try r.readByte(); 738 | if (byte == '\\') { 739 | byte = switch (r.readByte() catch 0) { 740 | 'n' => '\n', 741 | 'r' => '\r', 742 | 't' => '\t', 743 | else => byte, 744 | }; 745 | } else if (byte == '"') { 746 | break; 747 | } 748 | try bytes.append(byte); 749 | } 750 | const p = try atom.init(a); 751 | p.* = atom{ 752 | .str = bytes, 753 | }; 754 | return p; 755 | } 756 | 757 | fn parseIdent(a: std.mem.Allocator, br: anytype) LispError!*atom { 758 | const r = br.reader(); 759 | var bytes = std.ArrayList(u8).init(a); 760 | errdefer bytes.deinit(); 761 | loop: while (true) { 762 | switch (r.readByte() catch 0) { 763 | 'a'...'z', '0'...'9', '-', '+', '>', '<', '=' => |v| { 764 | try bytes.append(v); 765 | }, 766 | else => |v| { 767 | if (v != 0) try br.putBackByte(v); 768 | break :loop; 769 | }, 770 | } 771 | } 772 | const p = try atom.init(a); 773 | p.* = atom{ 774 | .sym = bytes, 775 | }; 776 | return p; 777 | } 778 | 779 | fn parseQuote(a: std.mem.Allocator, br: anytype) LispError!*atom { 780 | const r = br.reader(); 781 | const byte = try r.readByte(); 782 | if (byte != '\x27') return error.SyntaxError; 783 | 784 | const c = try parse(a, br); 785 | const p = try atom.init(a); 786 | p.* = atom{ .quote = c }; 787 | return p; 788 | } 789 | 790 | fn parseCell(a: std.mem.Allocator, br: anytype) LispError!*atom { 791 | const r = br.reader(); 792 | var byte = try r.readByte(); 793 | if (byte != '(') return error.SyntaxError; 794 | 795 | const top = try atom.init(a); 796 | top.* = atom{ 797 | .cell = cell{ 798 | .car = null, 799 | .cdr = null, 800 | }, 801 | }; 802 | var p = top; 803 | 804 | while (true) { 805 | p.cell.car = try parse(a, br); 806 | 807 | try skipWhilte(br); 808 | byte = try r.readByte(); 809 | if (byte == ')') { 810 | break; 811 | } 812 | try br.putBackByte(byte); 813 | 814 | const cdr = try atom.init(a); 815 | cdr.* = atom{ 816 | .cell = cell{ 817 | .car = null, 818 | .cdr = null, 819 | }, 820 | }; 821 | p.cell.cdr = cdr; 822 | p = p.cell.cdr.?; 823 | } 824 | return top; 825 | } 826 | 827 | fn parseNumber(a: std.mem.Allocator, br: anytype) LispError!*atom { 828 | const r = br.reader(); 829 | var bytes = std.ArrayList(u8).init(a); 830 | defer bytes.deinit(); 831 | loop: while (true) { 832 | switch (r.readByte() catch 0) { 833 | '0'...'9', '-', '+', 'e' => |v| try bytes.append(v), 834 | else => |v| { 835 | if (v != 0) try br.putBackByte(v); 836 | break :loop; 837 | }, 838 | } 839 | } 840 | 841 | if (std.fmt.parseInt(i64, bytes.items, 10)) |num| { 842 | const p = try atom.init(a); 843 | p.* = atom{ 844 | .num = num, 845 | }; 846 | return p; 847 | } else |_| { 848 | try br.putBack(bytes.items); 849 | return parseIdent(a, br); 850 | } 851 | } 852 | 853 | fn parse(a: std.mem.Allocator, br: anytype) LispError!*atom { 854 | try skipWhilte(br); 855 | const r = br.reader(); 856 | var byte = try r.readByte(); 857 | try br.putBackByte(byte); 858 | while (byte == ';') { 859 | try br.reader().skipUntilDelimiterOrEof('\n'); 860 | byte = try r.readByte(); 861 | try br.putBackByte(byte); 862 | } 863 | if (byte == ')') { 864 | const na = try atom.init(a); 865 | na.* = atom{ 866 | .cell = cell{ 867 | .car = null, 868 | .cdr = null, 869 | }, 870 | }; 871 | return na; 872 | } 873 | 874 | return switch (byte) { 875 | '(' => try parseCell(a, br), 876 | '0'...'9', '-', '+' => try parseNumber(a, br), 877 | 'a'...'z', '>', '<', '=' => try parseIdent(a, br), 878 | '\x27' => try parseQuote(a, br), 879 | '"' => try parseString(a, br), 880 | else => error.SyntaxError, 881 | }; 882 | } 883 | 884 | fn reader(r: anytype) bufReader(@TypeOf(r)) { 885 | return std.io.peekStream(2, r); 886 | } 887 | 888 | fn bufReader(comptime r: anytype) type { 889 | return std.io.PeekStream(std.fifo.LinearFifoBufferType{ .Static = 2 }, r); 890 | } 891 | 892 | fn run(a: std.mem.Allocator, br: anytype, repl: bool) !void { 893 | var e = env.init(a); 894 | defer e.deinit(); 895 | 896 | const t = try atom.init(a); 897 | t.* = atom{ 898 | .bool = true, 899 | }; 900 | try e.v.put("t", t); 901 | 902 | var gcValue = std.ArrayList(*atom).init(a); 903 | var gcAST = std.ArrayList(*atom).init(a); 904 | defer { 905 | for (gcValue.items) |value| { 906 | value.deinit(a, false); 907 | } 908 | for (gcAST.items) |value| { 909 | value.deinit(a, false); 910 | } 911 | gcValue.deinit(); 912 | } 913 | while (true) { 914 | if (repl and std.io.getStdIn().isTty()) { 915 | try std.io.getStdErr().writer().writeAll("> "); 916 | } 917 | if (parse(a, br)) |root| { 918 | if (eval(&e, a, root)) |result| { 919 | try gcValue.append(result); 920 | } else |err| { 921 | try e.printerr(err); 922 | return; 923 | } 924 | try gcAST.append(root); 925 | try std.io.getStdErr().writer().writeAll("\n"); 926 | } else |err| { 927 | if (err == error.EndOfStream) 928 | break; 929 | try e.printerr(err); 930 | if (!repl or !std.io.getStdIn().isTty()) { 931 | return err; 932 | } 933 | } 934 | } 935 | } 936 | 937 | pub fn main() anyerror!void { 938 | const a = std.heap.page_allocator; 939 | 940 | var args = try std.process.argsAlloc(a); 941 | defer std.process.argsFree(a, args); 942 | 943 | if (args.len == 1) { 944 | var bufr = reader(std.io.getStdIn().reader()); 945 | try run(a, &bufr, true); 946 | } else { 947 | for (args[1..]) |arg| { 948 | var f = try std.fs.cwd().openFile(arg, .{}); 949 | defer f.close(); 950 | var bufr = reader(f.reader()); 951 | try run(a, &bufr, false); 952 | } 953 | } 954 | } 955 | 956 | test "basic test" { 957 | const a = std.testing.allocator; 958 | 959 | const T = struct { input: []const u8, want: []const u8 }; 960 | const tests = [_]T{ 961 | .{ .input = "1", .want = "1\n" }, 962 | .{ .input = "(+ 1 2)", .want = "3\n" }, 963 | .{ .input = "(setq a 1)", .want = "1\n" }, 964 | .{ .input = "(setq a 1)(+ a 2)", .want = "1\n3\n" }, 965 | .{ .input = "(defun foo (a b) (+ a b))", .want = "foo\n" }, 966 | .{ .input = "(defun foo (a b) (+ a b))(foo 1 2)", .want = "foo\n3\n" }, 967 | .{ .input = "(concatenate \"foo\" \"bar\")", .want = "foobar\n" }, 968 | .{ .input = "'(1 2 3)", .want = "(1 2 3)\n" }, 969 | .{ .input = "(length '(1 2 3))", .want = "3\n" }, 970 | }; 971 | for (tests) |t| { 972 | var fs = std.io.fixedBufferStream(t.input); 973 | var br = reader(fs.reader()); 974 | 975 | var e = env.init(a); 976 | defer e.deinit(); 977 | 978 | var bytes = std.ArrayList(u8).init(a); 979 | defer bytes.deinit(); 980 | 981 | var gcValue = std.ArrayList(*atom).init(a); 982 | var gcAST = std.ArrayList(*atom).init(a); 983 | loop: while (true) { 984 | if (parse(a, &br)) |root| { 985 | var result = try eval(&e, a, root); 986 | try result.princ(bytes.writer(), false); 987 | try bytes.writer().writeByte('\n'); 988 | try gcValue.append(result); 989 | try gcAST.append(root); 990 | } else |_| { 991 | break :loop; 992 | } 993 | } 994 | for (gcValue.items) |value| { 995 | value.deinit(a, false); 996 | } 997 | for (gcAST.items) |value| { 998 | value.deinit(a, true); 999 | } 1000 | gcValue.deinit(); 1001 | gcAST.deinit(); 1002 | try std.testing.expect(std.mem.eql(u8, bytes.items, t.want)); 1003 | } 1004 | } 1005 | --------------------------------------------------------------------------------