lasp 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/DOCUMENTATION.md +942 -0
- data/EXAMPLES.md +57 -34
- data/README.md +7 -92
- data/bin/lasp +1 -1
- data/lib/lasp.rb +4 -2
- data/lib/lasp/corelib.rb +24 -22
- data/lib/lasp/errors.rb +4 -1
- data/lib/lasp/ext.rb +33 -0
- data/lib/lasp/fn.rb +2 -2
- data/lib/lasp/interpreter.rb +79 -0
- data/lib/lasp/params.rb +17 -8
- data/lib/lasp/parser.rb +22 -12
- data/lib/lasp/repl.rb +50 -29
- data/lib/lasp/stdlib.lasp +113 -109
- data/lib/lasp/stdmacros.lasp +31 -0
- data/lib/lasp/version.rb +1 -1
- metadata +5 -5
- data/lib/lasp/eval.rb +0 -66
- data/lib/lasp/parameters.rb +0 -93
- data/lib/lasp/representation.rb +0 -17
@@ -0,0 +1,79 @@
|
|
1
|
+
require "lasp/fn"
|
2
|
+
require "lasp/macro"
|
3
|
+
require "lasp/errors"
|
4
|
+
|
5
|
+
module Lasp
|
6
|
+
class Interpreter
|
7
|
+
def self.eval(form, env)
|
8
|
+
new.eval(form, env)
|
9
|
+
end
|
10
|
+
|
11
|
+
def eval(form, env)
|
12
|
+
case form
|
13
|
+
when Symbol then resolve_symbol(form, env)
|
14
|
+
when Array then eval_form(form, env)
|
15
|
+
else form
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def eval_form(form, env)
|
22
|
+
head, *tail = *form
|
23
|
+
|
24
|
+
case head
|
25
|
+
when :def then def_special_form(tail, env)
|
26
|
+
when :fn then fn_special_form(tail, env)
|
27
|
+
when :do then do_special_form(tail, env)
|
28
|
+
when :if then if_special_form(tail, env)
|
29
|
+
when :quote then quote_special_form(tail, env)
|
30
|
+
when :macro then macro_special_form(tail, env)
|
31
|
+
else call_function(head, tail, env)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def resolve_symbol(symbol, env)
|
36
|
+
env.fetch(symbol)
|
37
|
+
rescue KeyError
|
38
|
+
raise NameError, "#{symbol} is not present in this context"
|
39
|
+
end
|
40
|
+
|
41
|
+
def call_function(symbol, args, env)
|
42
|
+
fn = eval(symbol, env)
|
43
|
+
|
44
|
+
case fn
|
45
|
+
when Macro then eval(fn.(*args), env)
|
46
|
+
else fn.(*args.map { |form| eval(form, env) })
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def def_special_form(form, env)
|
51
|
+
key, value = form
|
52
|
+
fail ArgumentError, "you can only def symbols" unless Symbol === key
|
53
|
+
env[key] = eval(value, env)
|
54
|
+
end
|
55
|
+
|
56
|
+
def fn_special_form(form, env)
|
57
|
+
params, func = form
|
58
|
+
Fn.new(params, func, env)
|
59
|
+
end
|
60
|
+
|
61
|
+
def do_special_form(form, env)
|
62
|
+
form.map { |form| eval(form, env) }.last
|
63
|
+
end
|
64
|
+
|
65
|
+
def if_special_form(form, env)
|
66
|
+
conditional, true_form, false_form = form
|
67
|
+
eval(conditional, env) ? eval(true_form, env) : eval(false_form, env)
|
68
|
+
end
|
69
|
+
|
70
|
+
def quote_special_form(form, _)
|
71
|
+
form.first
|
72
|
+
end
|
73
|
+
|
74
|
+
def macro_special_form(form, env)
|
75
|
+
params, func = form
|
76
|
+
Macro.new(params, func, env)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/lasp/params.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "lasp/errors"
|
2
|
+
|
1
3
|
module Lasp
|
2
4
|
class Params
|
3
5
|
attr_reader :param_list
|
@@ -14,7 +16,7 @@ module Lasp
|
|
14
16
|
|
15
17
|
def rest
|
16
18
|
unless variadic?
|
17
|
-
fail
|
19
|
+
fail LaspError, "a non-variadic function does not have rest-arguments"
|
18
20
|
end
|
19
21
|
param_list.last
|
20
22
|
end
|
@@ -46,27 +48,34 @@ module Lasp
|
|
46
48
|
private
|
47
49
|
|
48
50
|
def validate_params!
|
51
|
+
validate_list!
|
49
52
|
validate_single_ampersand!
|
50
53
|
validate_single_rest_parameter!
|
51
54
|
validate_unique_parameter_names!
|
52
55
|
end
|
53
56
|
|
57
|
+
def validate_list!
|
58
|
+
unless Array === param_list
|
59
|
+
fail SyntaxError, "parameters must be a list"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
54
63
|
def validate_unique_parameter_names!
|
55
64
|
unless param_list.uniq.length == param_list.length
|
56
|
-
fail
|
65
|
+
fail SyntaxError, "parameter names have to be unique"
|
57
66
|
end
|
58
67
|
end
|
59
68
|
|
60
69
|
def validate_single_ampersand!
|
61
|
-
unless param_list.select { |p| p == :& }.length <= 1
|
62
|
-
fail ArgumentError, "Rest-arguments may only be used once, at the end, with a single binding."
|
63
|
-
end
|
70
|
+
invalid_rest_argument_usage! unless param_list.select { |p| p == :& }.length <= 1
|
64
71
|
end
|
65
72
|
|
66
73
|
def validate_single_rest_parameter!
|
67
|
-
if variadic? && param_list[-2] != :&
|
68
|
-
|
69
|
-
|
74
|
+
invalid_rest_argument_usage! if variadic? && param_list[-2] != :&
|
75
|
+
end
|
76
|
+
|
77
|
+
def invalid_rest_argument_usage!
|
78
|
+
fail SyntaxError, "rest-arguments may only be used once, at the end, with a single binding"
|
70
79
|
end
|
71
80
|
end
|
72
81
|
end
|
data/lib/lasp/parser.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
module Lasp
|
2
2
|
class Parser
|
3
|
+
def self.parse(program)
|
4
|
+
new.parse(program)
|
5
|
+
end
|
6
|
+
|
3
7
|
def parse(program)
|
4
8
|
build_ast(tokenize(sanitize(program)))
|
5
9
|
end
|
@@ -14,18 +18,24 @@ module Lasp
|
|
14
18
|
return if tokens.empty?
|
15
19
|
token = tokens.shift
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
case token
|
22
|
+
when "(" then form(tokens)
|
23
|
+
when "'" then quote(tokens)
|
24
|
+
else atom(token)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def form(tokens)
|
29
|
+
form = []
|
30
|
+
while tokens.first != ")"
|
31
|
+
form << build_ast(tokens)
|
28
32
|
end
|
33
|
+
tokens.shift
|
34
|
+
form
|
35
|
+
end
|
36
|
+
|
37
|
+
def quote(tokens)
|
38
|
+
[:quote] << build_ast(tokens)
|
29
39
|
end
|
30
40
|
|
31
41
|
def atom(token)
|
@@ -36,7 +46,7 @@ module Lasp
|
|
36
46
|
when /\A-?\d+\z/ then Integer(token)
|
37
47
|
when /\A-?\d+.\d+\z/ then Float(token)
|
38
48
|
when /"(.*)"/ then String($1)
|
39
|
-
when /:(
|
49
|
+
when /:([^\s]+)/ then String($1) # Symbol style strings are actually just strings
|
40
50
|
else token.to_sym
|
41
51
|
end
|
42
52
|
end
|
data/lib/lasp/repl.rb
CHANGED
@@ -1,43 +1,64 @@
|
|
1
1
|
require "lasp"
|
2
2
|
require "lasp/parser"
|
3
|
-
require "lasp/representation"
|
4
3
|
require "readline"
|
5
4
|
|
6
5
|
module Lasp
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
6
|
+
class Repl
|
7
|
+
def self.run
|
8
|
+
new.run
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
trap("SIGINT") { puts "\n\nBye!"; exit }
|
13
|
+
|
14
|
+
puts "((( Läsp v#{Lasp::VERSION} REPL (ctrl+c to exit) )))\n\n"
|
15
|
+
loop do
|
16
|
+
begin
|
17
|
+
history = true
|
18
|
+
input = Readline.readline(prompt, history).to_s
|
19
|
+
input = autoclose_parentheses(input)
|
20
|
+
result = Lasp::execute(input)
|
21
|
+
print_result(result)
|
22
|
+
rescue => error
|
23
|
+
print_error(error)
|
24
|
+
end
|
22
25
|
end
|
23
26
|
end
|
24
|
-
end
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
private
|
29
|
+
|
30
|
+
def autoclose_parentheses(input)
|
31
|
+
tokens = Parser.new.tokenize(input)
|
32
|
+
num_opens = tokens.select { |t| t == "(" }.count
|
33
|
+
num_closes = tokens.select { |t| t == ")" }.count
|
34
|
+
|
35
|
+
if num_opens > num_closes
|
36
|
+
missing_closes = num_opens - num_closes
|
37
|
+
|
38
|
+
print_info "Appending #{missing_closes} missing closing parentheses:"
|
39
|
+
corrected_input = input + (")" * missing_closes)
|
40
|
+
print_info "#{corrected_input}"
|
30
41
|
|
31
|
-
|
32
|
-
|
42
|
+
corrected_input
|
43
|
+
else
|
44
|
+
input
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def prompt
|
49
|
+
"lasp> "
|
50
|
+
end
|
33
51
|
|
34
|
-
|
35
|
-
|
36
|
-
|
52
|
+
def print_error(error)
|
53
|
+
puts " !> #{error.class}: #{error.message}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def print_info(message)
|
57
|
+
puts " ?> #{message}"
|
58
|
+
end
|
37
59
|
|
38
|
-
|
39
|
-
|
40
|
-
input
|
60
|
+
def print_result(result)
|
61
|
+
puts " => #{result.inspect}"
|
41
62
|
end
|
42
63
|
end
|
43
64
|
end
|
data/lib/lasp/stdlib.lasp
CHANGED
@@ -1,168 +1,172 @@
|
|
1
|
-
(require "
|
1
|
+
(require "stdmacros.lasp")
|
2
2
|
|
3
3
|
; Aliases
|
4
4
|
(def first head)
|
5
5
|
(def rest tail)
|
6
6
|
|
7
7
|
; Increment a number by one
|
8
|
-
(
|
8
|
+
(defn inc (x) (+ x 1))
|
9
9
|
|
10
10
|
; Decrement a number by one
|
11
|
-
(
|
11
|
+
(defn dec (x) (- x 1))
|
12
12
|
|
13
13
|
; Is it nil?
|
14
|
-
(
|
14
|
+
(defn nil? (arg) (= nil arg))
|
15
15
|
|
16
16
|
; If a list is empty
|
17
|
-
(
|
18
|
-
(
|
19
|
-
|
17
|
+
(defn empty?
|
18
|
+
(coll)
|
19
|
+
(nil? (head coll)))
|
20
20
|
|
21
21
|
; If all arguments are not equal
|
22
|
-
(
|
23
|
-
(
|
24
|
-
(not (apply = args))))
|
22
|
+
(defn not= (& args)
|
23
|
+
(not (apply = args)))
|
25
24
|
|
26
25
|
; The second item in a list
|
27
|
-
(
|
28
|
-
(
|
29
|
-
(head (tail coll))))
|
26
|
+
(defn second (coll)
|
27
|
+
(head (tail coll)))
|
30
28
|
|
31
29
|
; Modulus
|
32
|
-
(
|
33
|
-
(
|
30
|
+
(defn mod (x y)
|
31
|
+
(- x (* (/ x y) y)))
|
34
32
|
|
35
33
|
; Returns a function that does the opposite of the given function
|
36
|
-
(
|
37
|
-
(fn (
|
34
|
+
(defn complement (f)
|
35
|
+
(fn (x) (not (f x))))
|
38
36
|
|
39
37
|
; If a number is even
|
40
|
-
(
|
41
|
-
(
|
38
|
+
(defn even? (x)
|
39
|
+
(zero? (mod x 2)))
|
42
40
|
|
43
41
|
; If a number is odd
|
44
42
|
(def odd? (complement even?))
|
45
43
|
|
46
44
|
; If a number is zero
|
47
|
-
(
|
48
|
-
(
|
45
|
+
(defn zero? (x)
|
46
|
+
(= 0 x))
|
49
47
|
|
50
48
|
; Length of a list
|
51
|
-
(
|
52
|
-
(
|
53
|
-
|
54
|
-
|
55
|
-
(inc (len (tail coll))))))
|
49
|
+
(defn len (coll)
|
50
|
+
(if (empty? coll)
|
51
|
+
0
|
52
|
+
(inc (len (tail coll)))))
|
56
53
|
|
57
54
|
; Gets an item in a list by index
|
58
|
-
(
|
59
|
-
(
|
60
|
-
(
|
61
|
-
|
62
|
-
(nth (dec index) (tail coll)))))
|
55
|
+
(defn nth (index coll)
|
56
|
+
(if (zero? index)
|
57
|
+
(head coll)
|
58
|
+
(nth (dec index) (tail coll))))
|
63
59
|
|
64
60
|
; Last item in list
|
65
|
-
(
|
66
|
-
(
|
67
|
-
(nth (dec (len coll)) coll)))
|
61
|
+
(defn last (coll)
|
62
|
+
(nth (dec (len coll)) coll))
|
68
63
|
|
69
64
|
; Reverses a list
|
70
|
-
(
|
71
|
-
(
|
72
|
-
(
|
65
|
+
(defn reverse (coll)
|
66
|
+
(reduce
|
67
|
+
(fn (acc item) (cons item acc))
|
68
|
+
(list)
|
69
|
+
coll))
|
73
70
|
|
74
71
|
; Apply f to all items in list
|
75
|
-
(
|
76
|
-
(
|
77
|
-
|
78
|
-
|
79
|
-
(
|
80
|
-
|
81
|
-
(map f (tail coll))))))
|
72
|
+
(defn map (f coll)
|
73
|
+
(if (nil? (head coll))
|
74
|
+
coll
|
75
|
+
(cons
|
76
|
+
(f (head coll))
|
77
|
+
(map f (tail coll)))))
|
82
78
|
|
83
79
|
; Go through a list passing an accumulator and each item of the list through f
|
84
|
-
; f(acc item)
|
85
|
-
(
|
86
|
-
(
|
87
|
-
|
88
|
-
|
89
|
-
(reduce f (f acc (head coll)) (tail coll)))))
|
80
|
+
; f has the signature (acc item)
|
81
|
+
(defn reduce (f acc coll)
|
82
|
+
(if (empty? coll)
|
83
|
+
acc
|
84
|
+
(reduce f (f acc (head coll)) (tail coll))))
|
90
85
|
|
91
86
|
; Filter a list of items based on a function
|
92
|
-
(
|
93
|
-
(
|
94
|
-
(
|
95
|
-
|
96
|
-
|
97
|
-
(reverse coll))))
|
87
|
+
(defn filter (f coll)
|
88
|
+
(reduce
|
89
|
+
(fn (acc item) (if (f item) (cons item acc) acc))
|
90
|
+
(list)
|
91
|
+
(reverse coll)))
|
98
92
|
|
99
93
|
; Sum of all items in a list
|
100
|
-
(
|
101
|
-
(
|
102
|
-
(reduce + 0 coll)))
|
94
|
+
(defn sum (coll)
|
95
|
+
(reduce + 0 coll))
|
103
96
|
|
104
|
-
; Take
|
105
|
-
(
|
106
|
-
(
|
107
|
-
(
|
108
|
-
|
109
|
-
(cons (head coll) (take (dec num) (tail coll))))))
|
97
|
+
; Take n items from list
|
98
|
+
(defn take (n coll)
|
99
|
+
(if (zero? n)
|
100
|
+
(list)
|
101
|
+
(cons (head coll) (take (dec n) (tail coll)))))
|
110
102
|
|
111
103
|
; Drop x items from list
|
112
|
-
(
|
113
|
-
(
|
114
|
-
|
115
|
-
|
116
|
-
(drop (dec num) (tail coll)))))
|
104
|
+
(defn drop (num coll)
|
105
|
+
(if (zero? num)
|
106
|
+
coll
|
107
|
+
(drop (dec num) (tail coll))))
|
117
108
|
|
118
109
|
; Exclusive range
|
119
|
-
(
|
120
|
-
(
|
121
|
-
(
|
122
|
-
|
123
|
-
(cons from (range (inc from) to)))))
|
110
|
+
(defn range (from to)
|
111
|
+
(if (>= from to)
|
112
|
+
(list)
|
113
|
+
(cons from (range (inc from) to))))
|
124
114
|
|
125
115
|
; Highest value in list
|
126
|
-
(
|
127
|
-
(
|
128
|
-
(
|
129
|
-
|
130
|
-
|
131
|
-
(tail coll))))
|
116
|
+
(defn max (coll)
|
117
|
+
(reduce
|
118
|
+
(fn (acc item) (if (< acc item) item acc))
|
119
|
+
(head coll)
|
120
|
+
(tail coll)))
|
132
121
|
|
133
122
|
; Lowest value in list
|
134
|
-
(
|
135
|
-
(
|
136
|
-
(
|
137
|
-
|
138
|
-
|
139
|
-
|
123
|
+
(defn min (coll)
|
124
|
+
(reduce
|
125
|
+
(fn (acc item) (if (> acc item) item acc))
|
126
|
+
(head coll)
|
127
|
+
(tail coll)))
|
128
|
+
|
129
|
+
; The naming suggests that it is an implementation detail of `every`, that
|
130
|
+
; needs to be named to allow recursion. If it has value in itself, it should be
|
131
|
+
; renamed and documented.
|
132
|
+
(defn every* (n coll acc)
|
133
|
+
(if (empty? coll)
|
134
|
+
acc
|
135
|
+
(every*
|
136
|
+
n
|
137
|
+
(drop n coll)
|
138
|
+
(cons (first coll) acc))))
|
139
|
+
|
140
|
+
; Every Nth item in list
|
141
|
+
(defn every (n coll)
|
142
|
+
(reverse (every* n coll (list))))
|
140
143
|
|
141
144
|
; Takes a method from Ruby-land and returns a Lasp function
|
142
|
-
(
|
143
|
-
(fn (meth)
|
144
|
-
(fn (arg)
|
145
|
-
(. arg meth))))
|
145
|
+
(defn ruby-method (meth)
|
146
|
+
(fn (arg) (. arg meth)))
|
146
147
|
|
147
|
-
; Convert string to list
|
148
|
-
(def str->list (ruby-method :chars))
|
149
148
|
|
150
|
-
;
|
151
|
-
(def list
|
149
|
+
; Conversion functions
|
150
|
+
(def text->list (ruby-method :chars)) ; Convert text to list
|
151
|
+
(def list->text (ruby-method :join)) ; Convert list to text
|
152
|
+
(def ->text (ruby-method :to_s)) ; Convert to text
|
153
|
+
(def ->integer (ruby-method :to_i)) ; Convert to int
|
154
|
+
(def ->decimal (ruby-method :to_f)) ; Convert to decimal
|
152
155
|
|
153
|
-
; Convert most things to a string
|
154
|
-
(def ->str (ruby-method :to_s))
|
155
156
|
|
156
157
|
; Pass a value in order through a list of functions
|
157
|
-
(
|
158
|
-
(
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
(
|
167
|
-
|
168
|
-
|
158
|
+
(defn pipe (item & fns)
|
159
|
+
(if (empty? fns)
|
160
|
+
item
|
161
|
+
; Note that you need to take special care when recursing with rest
|
162
|
+
; arguments, hence the use of apply.
|
163
|
+
(apply pipe (cons ((head fns) item) (tail fns)))))
|
164
|
+
|
165
|
+
; Reverses text
|
166
|
+
(defn reverse-text (text)
|
167
|
+
(pipe text text->list reverse list->text))
|
168
|
+
|
169
|
+
; Reads an answer to a question
|
170
|
+
(defn prompt (question)
|
171
|
+
(print question)
|
172
|
+
(readln))
|