Commit | Line | Data |
---|---|---|
a650ae5b | 1 | {zip, map, apply, and-list, join, Obj, concat, all} = require 'prelude-ls' |
3181c695 | 2 | {pr_str} = require './printer' |
25bb14c9 JB |
3 | {read_str} = require './reader' |
4 | fs = require 'fs' | |
3181c695 JB |
5 | |
6 | ||
7 | export runtime-error = (msg) -> throw new Error msg | |
8 | ||
9 | ||
10 | fn = (body) -> {type: \function, value: body} | |
2ff2d84b | 11 | const-nil = -> {type: \const, value: \nil} |
3181c695 JB |
12 | const-int = (int) -> {type: \int, value: int} |
13 | const-bool = (bool) -> {type: \const, value: if bool then \true else \false} | |
14 | const-str = (str) -> {type: \string, value: str} | |
15 | ||
16 | list-or-vector = ({type}) -> type in [\list \vector] | |
17 | ||
18 | deep-equals = (a, b) -> | |
19 | if not list-or-vector a then | |
20 | if a.type != b.type then false | |
21 | else a.value == b.value | |
22 | else if list-or-vector b then | |
23 | if a.value.length != b.value.length then false | |
24 | else | |
25 | # Compare all elements of a and b with deep-equals. | |
26 | zip a.value, b.value | |
27 | |> map (apply deep-equals) | |
28 | |> and-list # all must be true (equals) | |
29 | else false | |
30 | ||
31 | ||
a650ae5b JB |
32 | check-param = (name, idx, test, expected, actual) -> |
33 | if not test | |
34 | runtime-error "'#{name}' expected parameter #{idx} | |
35 | to be #{expected}, got #{actual}" | |
36 | ||
37 | ||
38 | check-type = (name, idx, expected, actual) -> | |
39 | check-param name, idx, expected == actual, expected, actual | |
25bb14c9 JB |
40 | |
41 | ||
3181c695 JB |
42 | export ns = do |
43 | '+': fn (a, b) -> const-int a.value + b.value | |
44 | '-': fn (a, b) -> const-int a.value - b.value | |
45 | '*': fn (a, b) -> const-int a.value * b.value | |
46 | '/': fn (a, b) -> const-int parseInt (a.value / b.value) | |
47 | ||
48 | 'list': fn (...list) -> {type: \list, value: list} | |
49 | 'list?': fn (param) -> const-bool param.type == \list | |
50 | ||
2ff2d84b JB |
51 | 'empty?': fn ({type, value}) -> |
52 | switch type | |
53 | | \const => | |
54 | if value == \nil | |
55 | then const-bool true | |
56 | else runtime-error "'empty?' is not supported on #{value}" | |
57 | | \list, \vector => | |
58 | const-bool value.length == 0 | |
59 | | \map => | |
60 | const-bool Obj.empty value | |
61 | | otherwise => | |
62 | runtime-error "'empty?' is not supported on type #{type}" | |
63 | ||
64 | 'count': fn ({type, value}) -> | |
65 | switch type | |
66 | | \const => | |
67 | if value == \nil | |
68 | then const-int 0 | |
69 | else runtime-error "'count' is not supported on #{value}" | |
70 | | \list, \vector => | |
71 | const-int value.length | |
72 | | \map => | |
73 | value |> Obj.keys |> (.length) |> const-int | |
74 | | otherwise => | |
75 | runtime-error "'count' is not supported on type #{type}" | |
3181c695 JB |
76 | |
77 | '=': fn (a, b) -> const-bool (deep-equals a, b) | |
78 | '<': fn (a, b) -> const-bool a.value < b.value | |
79 | '>': fn (a, b) -> const-bool a.value > b.value | |
80 | '<=': fn (a, b) -> const-bool a.value <= b.value | |
81 | '>=': fn (a, b) -> const-bool a.value >= b.value | |
82 | ||
2ff2d84b JB |
83 | 'not': fn ({type, value}) -> |
84 | const-bool (type == \const and value == \false) | |
85 | ||
86 | 'pr-str': fn (...params) -> | |
87 | params |> map (p) -> pr_str p, print_readably=true | |
88 | |> join ' ' | |
89 | |> const-str | |
90 | ||
91 | 'str': fn (...params) -> | |
92 | params |> map (p) -> pr_str p, print_readably=false | |
93 | |> join '' | |
94 | |> const-str | |
95 | ||
96 | 'prn': fn (...params) -> | |
97 | params |> map (p) -> pr_str p, print_readably=true | |
98 | |> join ' ' | |
99 | |> console.log | |
100 | |> const-nil | |
101 | ||
102 | 'println': fn (...params) -> | |
103 | params |> map (p) -> pr_str p, print_readbly=false | |
104 | |> join ' ' | |
105 | |> console.log | |
106 | |> const-nil | |
25bb14c9 JB |
107 | |
108 | 'read-string': fn ({type, value}) -> | |
a650ae5b | 109 | check-type 'read-string', 0, \string, type |
25bb14c9 JB |
110 | read_str value |
111 | ||
112 | 'slurp': fn (filename) -> | |
113 | if filename.type != \string | |
114 | runtime-error "'slurp' expected the first parameter | |
115 | to be a string, got a #{filename.type}" | |
116 | ||
117 | const-str <| fs.readFileSync filename.value, 'utf8' | |
118 | ||
119 | 'atom': fn (value) -> {type: \atom, value: value} | |
120 | 'atom?': fn (atom) -> const-bool atom.type == \atom | |
121 | 'deref': fn (atom) -> | |
a650ae5b | 122 | check-type 'deref', 0, \atom, atom.type |
25bb14c9 JB |
123 | atom.value |
124 | ||
125 | 'reset!': fn (atom, value) -> | |
a650ae5b | 126 | check-type 'reset!', 0, \atom, atom.type |
25bb14c9 JB |
127 | atom.value = value |
128 | ||
129 | 'swap!': fn (atom, fn, ...args) -> | |
a650ae5b | 130 | check-type 'swap!', 0, \atom, atom.type |
25bb14c9 JB |
131 | if fn.type != \function |
132 | runtime-error "'swap!' expected the second parameter | |
133 | to be a function, got a #{fn.type}" | |
134 | ||
135 | atom.value = fn.value.apply @, [atom.value] ++ args | |
136 | if atom.value.type == \tco # TODO make this a method. | |
137 | atom.value = atom.value.eval! | |
138 | ||
139 | atom.value | |
a650ae5b JB |
140 | |
141 | 'cons': fn (value, list) -> | |
142 | check-param 'cons', 1, (list-or-vector list), | |
143 | 'list or vector', list.type | |
144 | ||
145 | {type: \list, value: [value] ++ list.value} | |
146 | ||
147 | 'concat': fn (...params) -> | |
148 | if not all list-or-vector, params | |
149 | runtime-error "'concat' expected all parameters to be a list or vector" | |
150 | ||
151 | {type: \list, value: params |> map (.value) |> concat} |