Step 7
[jackhill/mal.git] / livescript / core.ls
CommitLineData
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'
4fs = require 'fs'
3181c695
JB
5
6
7export runtime-error = (msg) -> throw new Error msg
8
9
10fn = (body) -> {type: \function, value: body}
2ff2d84b 11const-nil = -> {type: \const, value: \nil}
3181c695
JB
12const-int = (int) -> {type: \int, value: int}
13const-bool = (bool) -> {type: \const, value: if bool then \true else \false}
14const-str = (str) -> {type: \string, value: str}
15
16list-or-vector = ({type}) -> type in [\list \vector]
17
18deep-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
32check-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
38check-type = (name, idx, expected, actual) ->
39 check-param name, idx, expected == actual, expected, actual
25bb14c9
JB
40
41
3181c695
JB
42export 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}