| @@ -56,13 +56,15 @@ type |
| 56 | cnt*: int | 56 | cnt*: int |
| 57 | nextOrd*: int | 57 | nextOrd*: int |
| 58 | | 58 | |
| 59 | - Value* = ref ValueObj | 59 | + Obj* = ref ValueObj |
| | 60 | + ## Value itself is not a ref: nil, bool, int and float live entirely in the |
| | 61 | + ## word below, so arithmetic and a numeric sequence never touch the heap. Only |
| | 62 | + ## the kinds with a real payload allocate a ValueObj, which is also what keeps |
| | 63 | + ## a lazy seq's memoisation working -- `force` mutates through `obj`, which is |
| | 64 | + ## still shared by every Value that names it. |
| 60 | ValueObj* = object | 65 | ValueObj* = object |
| 61 | case kind*: Kind | 66 | case kind*: Kind |
| 62 | - of kNil: discard | 67 | + of kNil, kBool, kInt, kFloat: discard |
| 63 | - of kBool: b*: bool | | |
| 64 | - of kInt: i*: int64 | | |
| 65 | - of kFloat: f*: float64 | | |
| 66 | of kStr, kKeyword, kSymbol: s*: string | 68 | of kStr, kKeyword, kSymbol: s*: string |
| 67 | of kList: xs*: seq[Value] | 69 | of kList: xs*: seq[Value] |
| 68 | of kVector: vec*: PVec | 70 | of kVector: vec*: PVec |
| @@ -84,8 +86,37 @@ type |
| 84 | fn*: proc (args: openArray[Value]): Value {.closure.} | 86 | fn*: proc (args: openArray[Value]): Value {.closure.} |
| 85 | name*: string | 87 | name*: string |
| 86 | | 88 | |
| | 89 | + Value* = object |
| | 90 | + kind*: Kind |
| | 91 | + raw*: int64 ## int payload; a float is bit-cast into it; bool is 0/1 |
| | 92 | + obj*: Obj ## nil for the scalar kinds |
| | 93 | + |
| 87 | CljError* = object of CatchableError | 94 | CljError* = object of CatchableError |
| 88 | | 95 | |
| | 96 | +# Field accessors, named so the rest of the codebase keeps reading `v.i`, |
| | 97 | +# `v.xs`, `v.head` exactly as it did when Value was a ref to the variant. |
| | 98 | +proc i*(v: Value): int64 {.inline.} = v.raw |
| | 99 | +proc b*(v: Value): bool {.inline.} = v.raw != 0 |
| | 100 | +proc f*(v: Value): float64 {.inline.} = cast[float64](v.raw) |
| | 101 | +proc s*(v: Value): lent string {.inline.} = v.obj.s |
| | 102 | +proc xs*(v: Value): lent seq[Value] {.inline.} = v.obj.xs |
| | 103 | +proc vec*(v: Value): lent PVec {.inline.} = v.obj.vec |
| | 104 | +proc m*(v: Value): lent PMap {.inline.} = v.obj.m |
| | 105 | +proc head*(v: Value): lent Value {.inline.} = v.obj.head |
| | 106 | +proc tl*(v: Value): lent Value {.inline.} = v.obj.tl |
| | 107 | +proc chunk*(v: Value): lent seq[Value] {.inline.} = v.obj.chunk |
| | 108 | +proc coff*(v: Value): int {.inline.} = v.obj.coff |
| | 109 | +proc ctl*(v: Value): lent Value {.inline.} = v.obj.ctl |
| | 110 | +proc cached*(v: Value): lent Value {.inline.} = v.obj.cached |
| | 111 | +proc forced*(v: Value): bool {.inline.} = v.obj.forced |
| | 112 | +proc thunk*(v: Value): auto {.inline.} = v.obj.thunk |
| | 113 | +proc fn*(v: Value): auto {.inline.} = v.obj.fn |
| | 114 | +proc name*(v: Value): lent string {.inline.} = v.obj.name |
| | 115 | + |
| | 116 | +## A Value is never a nil pointer now, but every `v.isNil` in the codebase |
| | 117 | +## meant "is this the nil value", so keep the spelling working. |
| | 118 | +proc isNil*(v: Value): bool {.inline.} = v.kind == kNil |
| | 119 | + |
| 89 | # ------------------------------------------------------------ teardown | 120 | # ------------------------------------------------------------ teardown |
| 90 | ## A realized lazy seq is a chain of `cons -> lazy -> cons -> …` refs, and ARC | 121 | ## A realized lazy seq is a chain of `cons -> lazy -> cons -> …` refs, and ARC |
| 91 | ## frees a chain by recursing into it — a million-element seq means a million | 122 | ## frees a chain by recursing into it — a million-element seq means a million |
| @@ -130,8 +161,8 @@ proc `=destroy`*(x: var ValueObj) = |
| 130 | draining = false | 161 | draining = false |
| 131 | | 162 | |
| 132 | let NilV* = Value(kind: kNil) | 163 | let NilV* = Value(kind: kNil) |
| 133 | -let TrueV* = Value(kind: kBool, b: true) | 164 | +let TrueV* = Value(kind: kBool, raw: 1) |
| 134 | -let FalseV* = Value(kind: kBool, b: false) | 165 | +let FalseV* = Value(kind: kBool, raw: 0) |
| 135 | | 166 | |
| 136 | proc err*(msg: string) {.noreturn.} = raise newException(CljError, msg) | 167 | proc err*(msg: string) {.noreturn.} = raise newException(CljError, msg) |
| 137 | | 168 | |
| @@ -382,27 +413,27 @@ proc mapEntries*(m: PMap): seq[MEntry] = |
| 382 | | 413 | |
| 383 | # ------------------------------------------------------------ constructors | 414 | # ------------------------------------------------------------ constructors |
| 384 | proc mkBool*(x: bool): Value = (if x: TrueV else: FalseV) | 415 | proc mkBool*(x: bool): Value = (if x: TrueV else: FalseV) |
| 385 | -proc mkInt*(x: int64): Value = Value(kind: kInt, i: x) | 416 | +proc mkInt*(x: int64): Value {.inline.} = Value(kind: kInt, raw: x) |
| 386 | -proc mkFloat*(x: float64): Value = Value(kind: kFloat, f: x) | 417 | +proc mkFloat*(x: float64): Value {.inline.} = Value(kind: kFloat, raw: cast[int64](x)) |
| 387 | -proc mkStr*(x: string): Value = Value(kind: kStr, s: x) | 418 | +proc mkStr*(x: string): Value = Value(kind: kStr, obj: Obj(kind: kStr, s: x)) |
| 388 | -proc mkKeyword*(x: string): Value = Value(kind: kKeyword, s: x) | 419 | +proc mkKeyword*(x: string): Value = Value(kind: kKeyword, obj: Obj(kind: kKeyword, s: x)) |
| 389 | -proc mkSymbol*(x: string): Value = Value(kind: kSymbol, s: x) | 420 | +proc mkSymbol*(x: string): Value = Value(kind: kSymbol, obj: Obj(kind: kSymbol, s: x)) |
| 390 | -proc mkList*(xs: seq[Value]): Value = Value(kind: kList, xs: xs) | 421 | +proc mkList*(xs: seq[Value]): Value = Value(kind: kList, obj: Obj(kind: kList, xs: xs)) |
| 391 | -proc mkVector*(xs: seq[Value]): Value = Value(kind: kVector, vec: toPVec(xs)) | 422 | +proc mkVector*(xs: seq[Value]): Value = Value(kind: kVector, obj: Obj(kind: kVector, vec: toPVec(xs))) |
| 392 | -proc mkVec*(v: PVec): Value = Value(kind: kVector, vec: v) | 423 | +proc mkVec*(v: PVec): Value = Value(kind: kVector, obj: Obj(kind: kVector, vec: v)) |
| 393 | -proc mkMapOf*(m: PMap): Value = Value(kind: kMap, m: m) | 424 | +proc mkMapOf*(m: PMap): Value = Value(kind: kMap, obj: Obj(kind: kMap, m: m)) |
| 394 | -proc mkSetOf*(m: PMap): Value = Value(kind: kSet, m: m) | 425 | +proc mkSetOf*(m: PMap): Value = Value(kind: kSet, obj: Obj(kind: kSet, m: m)) |
| 395 | | 426 | |
| 396 | proc mkMap*(ps: seq[(Value, Value)]): Value = | 427 | proc mkMap*(ps: seq[(Value, Value)]): Value = |
| 397 | var m = emptyPMap() | 428 | var m = emptyPMap() |
| 398 | for (k, v) in ps: m = mapAssoc(m, k, v) | 429 | for (k, v) in ps: m = mapAssoc(m, k, v) |
| 399 | - Value(kind: kMap, m: m) | 430 | + mkMapOf(m) |
| 400 | | 431 | |
| 401 | proc mkSet*(xs: seq[Value]): Value = | 432 | proc mkSet*(xs: seq[Value]): Value = |
| 402 | var m = emptyPMap() | 433 | var m = emptyPMap() |
| 403 | for x in xs: | 434 | for x in xs: |
| 404 | if not mapContains(m, x): m = mapAssoc(m, x, x) | 435 | if not mapContains(m, x): m = mapAssoc(m, x, x) |
| 405 | - Value(kind: kSet, m: m) | 436 | + mkSetOf(m) |
| 406 | | 437 | |
| 407 | # openArray overloads, so a builtin can pass its argument list straight through | 438 | # openArray overloads, so a builtin can pass its argument list straight through |
| 408 | # without first copying it into a seq. | 439 | # without first copying it into a seq. |
| @@ -411,7 +442,7 @@ proc mkVector*(xs: openArray[Value]): Value = mkVector(@xs) |
| 411 | proc mkSet*(xs: openArray[Value]): Value = mkSet(@xs) | 442 | proc mkSet*(xs: openArray[Value]): Value = mkSet(@xs) |
| 412 | | 443 | |
| 413 | proc mkFn*(name: string, f: proc (args: openArray[Value]): Value {.closure.}): Value = | 444 | proc mkFn*(name: string, f: proc (args: openArray[Value]): Value {.closure.}): Value = |
| 414 | - Value(kind: kFn, fn: f, name: name) | 445 | + Value(kind: kFn, obj: Obj(kind: kFn, fn: f, name: name)) |
| 415 | | 446 | |
| 416 | # --------------------------------------------------------------- lazy seqs | 447 | # --------------------------------------------------------------- lazy seqs |
| 417 | ## A lazy seq is a thunk that, when forced, yields either nil/`kNil` (the end) | 448 | ## A lazy seq is a thunk that, when forced, yields either nil/`kNil` (the end) |
| @@ -421,7 +452,7 @@ proc mkFn*(name: string, f: proc (args: openArray[Value]): Value {.closure.}): V |
| 421 | ## in core, which is what keeps `(nth (iterate inc 0) 1000000)` from blowing the | 452 | ## in core, which is what keeps `(nth (iterate inc 0) 1000000)` from blowing the |
| 422 | ## stack. | 453 | ## stack. |
| 423 | | 454 | |
| 424 | -proc mkCons*(h, t: Value): Value = Value(kind: kCons, head: h, tl: t) | 455 | +proc mkCons*(h, t: Value): Value = Value(kind: kCons, obj: Obj(kind: kCons, head: h, tl: t)) |
| 425 | | 456 | |
| 426 | const ChunkSize* = 32 | 457 | const ChunkSize* = 32 |
| 427 | ## Elements realized per step by a chunked producer. The same batch size | 458 | ## Elements realized per step by a chunked producer. The same batch size |
| @@ -431,21 +462,21 @@ const ChunkSize* = 32 |
| 431 | proc mkChunk*(xs: sink seq[Value], off: int, rest: Value): Value = | 462 | proc mkChunk*(xs: sink seq[Value], off: int, rest: Value): Value = |
| 432 | ## Normalizes an exhausted chunk away, so a cursor never sees an empty run. | 463 | ## Normalizes an exhausted chunk away, so a cursor never sees an empty run. |
| 433 | if off >= xs.len: return (if rest.isNil: NilV else: rest) | 464 | if off >= xs.len: return (if rest.isNil: NilV else: rest) |
| 434 | - Value(kind: kChunk, chunk: xs, coff: off, ctl: rest) | 465 | + Value(kind: kChunk, obj: Obj(kind: kChunk, chunk: xs, coff: off, ctl: rest)) |
| 435 | | 466 | |
| 436 | proc mkLazy*(f: proc (): Value {.closure.}): Value = | 467 | proc mkLazy*(f: proc (): Value {.closure.}): Value = |
| 437 | - Value(kind: kLazy, thunk: f, cached: nil, forced: false) | 468 | + Value(kind: kLazy, obj: Obj(kind: kLazy, thunk: f, cached: NilV, forced: false)) |
| 438 | | 469 | |
| 439 | proc force*(v: Value): Value = | 470 | proc force*(v: Value): Value = |
| 440 | ## Realize one step: follow a chain of lazy seqs down to a cons, a concrete | 471 | ## Realize one step: follow a chain of lazy seqs down to a cons, a concrete |
| 441 | ## collection, or the end of the seq. | 472 | ## collection, or the end of the seq. |
| 442 | var cur = v | 473 | var cur = v |
| 443 | - while not cur.isNil and cur.kind == kLazy: | 474 | + while cur.kind == kLazy: |
| 444 | - if not cur.forced: | 475 | + if not cur.obj.forced: |
| 445 | - cur.cached = cur.thunk() | 476 | + cur.obj.cached = cur.obj.thunk() |
| 446 | - cur.forced = true | 477 | + cur.obj.forced = true |
| 447 | - cur.thunk = nil # drop the closure so its captures can be collected | 478 | + cur.obj.thunk = nil # drop the closure so its captures can be collected |
| 448 | - cur = cur.cached | 479 | + cur = cur.obj.cached |
| 449 | cur | 480 | cur |
| 450 | | 481 | |
| 451 | proc isSeqNode(v: Value): bool = | 482 | proc isSeqNode(v: Value): bool = |
| @@ -471,17 +502,15 @@ proc cursor*(v: Value): Cursor = |
| 471 | | 502 | |
| 472 | proc hasNext*(c: var Cursor): bool = | 503 | proc hasNext*(c: var Cursor): bool = |
| 473 | while true: | 504 | while true: |
| 474 | - if not c.run.isNil: | 505 | + if c.run.kind == kChunk: |
| 475 | if c.idx < c.run.chunk.len: return true | 506 | if c.idx < c.run.chunk.len: return true |
| 476 | - c.run = nil | 507 | + c.run = NilV |
| 477 | c.idx = 0 | 508 | c.idx = 0 |
| 478 | elif c.idx < c.backing.len: | 509 | elif c.idx < c.backing.len: |
| 479 | return true | 510 | return true |
| 480 | - if c.node.isNil: return false | 511 | + if c.node.kind == kNil: return false |
| 481 | c.node = force(c.node) | 512 | c.node = force(c.node) |
| 482 | - if c.node.isNil or c.node.kind == kNil: | 513 | + if c.node.kind == kNil: return false |
| 483 | - c.node = nil | | |
| 484 | - return false | | |
| 485 | case c.node.kind | 514 | case c.node.kind |
| 486 | of kCons: | 515 | of kCons: |
| 487 | return true # the element is c.node.head | 516 | return true # the element is c.node.head |
| @@ -495,10 +524,10 @@ proc hasNext*(c: var Cursor): bool = |
| 495 | # does. Switch to indexing it rather than reporting the seq as finished. | 524 | # does. Switch to indexing it rather than reporting the seq as finished. |
| 496 | c.backing = toSeq(c.node) | 525 | c.backing = toSeq(c.node) |
| 497 | c.idx = 0 | 526 | c.idx = 0 |
| 498 | - c.node = nil | 527 | + c.node = NilV |
| 499 | | 528 | |
| 500 | proc next*(c: var Cursor): Value = | 529 | proc next*(c: var Cursor): Value = |
| 501 | - if not c.run.isNil: | 530 | + if c.run.kind == kChunk: |
| 502 | result = c.run.chunk[c.idx] | 531 | result = c.run.chunk[c.idx] |
| 503 | inc c.idx | 532 | inc c.idx |
| 504 | elif c.idx < c.backing.len: | 533 | elif c.idx < c.backing.len: |
| @@ -577,7 +606,7 @@ proc hashValue*(v: Value): uint32 = |
| 577 | for e in mapEntries(v.m): | 606 | for e in mapEntries(v.m): |
| 578 | h = h xor mixHash(hashValue(e.key), hashValue(e.val)) | 607 | h = h xor mixHash(hashValue(e.key), hashValue(e.val)) |
| 579 | h | 608 | h |
| 580 | - of kFn: uint32(hash(cast[int](cast[pointer](v)))) | 609 | + of kFn: uint32(hash(cast[int](cast[pointer](v.obj)))) |
| 581 | | 610 | |
| 582 | # --------------------------------------------------------------- accessors | 611 | # --------------------------------------------------------------- accessors |
| 583 | proc items*(v: Value): seq[Value] = | 612 | proc items*(v: Value): seq[Value] = |
| @@ -620,7 +649,7 @@ proc count*(v: Value): int = |
| 620 | else: err("Don't know how to count: " & prStr(v)) | 649 | else: err("Don't know how to count: " & prStr(v)) |
| 621 | | 650 | |
| 622 | proc truthy*(v: Value): bool = | 651 | proc truthy*(v: Value): bool = |
| 623 | - if v == nil: return false | 652 | + if v.kind == kNil: return false |
| 624 | case v.kind | 653 | case v.kind |
| 625 | of kNil: false | 654 | of kNil: false |
| 626 | of kBool: v.b | 655 | of kBool: v.b |
| @@ -656,7 +685,7 @@ proc equals*(a, b: Value): bool = |
| 656 | true | 685 | true |
| 657 | of kMap: | 686 | of kMap: |
| 658 | if a.m.cnt != b.m.cnt: return false | 687 | if a.m.cnt != b.m.cnt: return false |
| 659 | - let missing = Value(kind: kKeyword, s: "%clonim-missing") | 688 | + let missing = mkKeyword("%clonim-missing") |
| 660 | for e in mapEntries(a.m): | 689 | for e in mapEntries(a.m): |
| 661 | if not equals(e.val, mapGet(b.m, e.key, missing)): return false | 690 | if not equals(e.val, mapGet(b.m, e.key, missing)): return false |
| 662 | true | 691 | true |
| @@ -746,7 +775,7 @@ proc hasVar*(name: string): bool = |
| 746 | proc call*(f: Value, args: openArray[Value]): Value = | 775 | proc call*(f: Value, args: openArray[Value]): Value = |
| 747 | if f.isNil: err("Can't call nil") | 776 | if f.isNil: err("Can't call nil") |
| 748 | case f.kind | 777 | case f.kind |
| 749 | - of kFn: f.fn(args) | 778 | + of kFn: f.obj.fn(args) |
| 750 | of kKeyword: | 779 | of kKeyword: |
| 751 | # (:k m) => lookup | 780 | # (:k m) => lookup |
| 752 | if args.len == 0: err("Wrong number of args to keyword") | 781 | if args.len == 0: err("Wrong number of args to keyword") |