risp 0.0.1
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.
- data/README +65 -0
- data/Rakefile +65 -0
- data/bin/risp +79 -0
- data/lib/risp.rb +522 -0
- data/test/test_risp_reader.rb +25 -0
- metadata +82 -0
data/README
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
= Risp
|
2
|
+
|
3
|
+
Risp (Ruby lisp) is a quite tiny, toy interpreter of lisp.
|
4
|
+
|
5
|
+
For almost people, it seems not to be appropriate Lisp --
|
6
|
+
it lacks even a macro!!
|
7
|
+
|
8
|
+
Features: variables are bound to Lisp-1 namespace, there's no
|
9
|
+
special-form which is appropriate `defun'. It means a scheme's
|
10
|
+
way is invalid. Please use lambda.
|
11
|
+
|
12
|
+
(define (hoge x) ;; raise type error for list
|
13
|
+
(+ x 1))
|
14
|
+
|
15
|
+
There's no package system, macro and beautiful API.
|
16
|
+
|
17
|
+
|
18
|
+
+ Types
|
19
|
+
* integer
|
20
|
+
* string
|
21
|
+
* t
|
22
|
+
* nil
|
23
|
+
* symbol
|
24
|
+
* list
|
25
|
+
* function
|
26
|
+
|
27
|
+
+ Builtin functions and special-forms
|
28
|
+
* car
|
29
|
+
* cdr
|
30
|
+
* cons
|
31
|
+
* eq
|
32
|
+
* atom
|
33
|
+
* quote
|
34
|
+
* cond
|
35
|
+
* define
|
36
|
+
* lambda
|
37
|
+
* +
|
38
|
+
|
39
|
+
|
40
|
+
= Install
|
41
|
+
|
42
|
+
$ gem install risp
|
43
|
+
|
44
|
+
|
45
|
+
= Usage
|
46
|
+
|
47
|
+
$ risp somefile.l
|
48
|
+
|
49
|
+
$ risp -i
|
50
|
+
|
51
|
+
|
52
|
+
= Dependence
|
53
|
+
|
54
|
+
Risp is developed on:
|
55
|
+
ruby 1.9.1p429 (2010-07-02 revision 28523) [i386-mswin32]
|
56
|
+
|
57
|
+
|
58
|
+
= Lisence
|
59
|
+
|
60
|
+
Under the modified BSD lisence.
|
61
|
+
|
62
|
+
|
63
|
+
---
|
64
|
+
|
65
|
+
arikui <arikui.ruby@gmail.com>
|
data/Rakefile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require_relative 'lib/risp'
|
6
|
+
|
7
|
+
LIBS = FileList["lib/*.rb"]
|
8
|
+
TESTS = FileList["test/test_*.rb"]
|
9
|
+
|
10
|
+
task :default => :test
|
11
|
+
|
12
|
+
gemspec = Gem::Specification.new do |s|
|
13
|
+
s.name = %q{risp}
|
14
|
+
s.version = Risp.version
|
15
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
16
|
+
s.authors = ["arikui"]
|
17
|
+
s.date = %q{2010-11-19}
|
18
|
+
s.description = %q{A toy lisp interpreter.}
|
19
|
+
s.email = %q{arikui.ruby@gmail.com}
|
20
|
+
s.executable = 'risp'
|
21
|
+
s.extra_rdoc_files = ["README"]
|
22
|
+
s.files = ["README",
|
23
|
+
"Rakefile",
|
24
|
+
"bin/risp",
|
25
|
+
*TESTS, *LIBS]
|
26
|
+
s.homepage = %q{http://wiki.github.com/arikui1911/risp}
|
27
|
+
s.rdoc_options = ["--title", "risp documentation",
|
28
|
+
"--charset", "utf-8",
|
29
|
+
"--opname", "index.html",
|
30
|
+
"--line-numbers",
|
31
|
+
"--main", "README",
|
32
|
+
"--inline-source",
|
33
|
+
"--exclude", "^(examples|extras)/"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubyforge_project = %q{risp}
|
36
|
+
s.rubygems_version = %q{1.3.7}
|
37
|
+
s.summary = %q{A toy lisp interpreter; provides `pure-lisp' functions and Lisp-1 namespaces. Currently it lacks package system and macro(!!).}
|
38
|
+
s.test_files = TESTS.to_a
|
39
|
+
if s.respond_to? :specification_version then
|
40
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
41
|
+
s.specification_version = 3
|
42
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
43
|
+
else
|
44
|
+
end
|
45
|
+
else
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
gem_task = Rake::GemPackageTask.new(gemspec)
|
50
|
+
gem_task.define
|
51
|
+
|
52
|
+
desc "Upload the gem file #{gem_task.gem_file}"
|
53
|
+
task 'cutter' => 'gem' do |t|
|
54
|
+
gem "push #{gem_task.package_dir}/#{gem_task.gem_file}"
|
55
|
+
end
|
56
|
+
|
57
|
+
Rake::TestTask.new do |t|
|
58
|
+
t.warning = true
|
59
|
+
end
|
60
|
+
|
61
|
+
Rake::RDocTask.new do |t|
|
62
|
+
t.rdoc_dir = 'doc'
|
63
|
+
t.rdoc_files = LIBS.include("README")
|
64
|
+
t.options.push '-S', '-N'
|
65
|
+
end
|
data/bin/risp
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright (c) 2010, arikui
|
4
|
+
# All rights reserved.
|
5
|
+
|
6
|
+
# Redistribution and use in source and binary forms, with or
|
7
|
+
# without modification, are permitted provided that the
|
8
|
+
# following conditions are met:
|
9
|
+
|
10
|
+
# * Redistributions of source code must retain the above
|
11
|
+
# copyright notice, this list of conditions and the
|
12
|
+
# following disclaimer.
|
13
|
+
# * Redistributions in binary form must reproduce the above
|
14
|
+
# copyright notice, this list of conditions and the following
|
15
|
+
# disclaimer in the documentation and/or other materials
|
16
|
+
# provided with the distribution.
|
17
|
+
# * Neither the name of the arikui-risp nor the names of its
|
18
|
+
# contributors may be used to endorse or promote products
|
19
|
+
# derived from this software without specific prior written
|
20
|
+
# permission.
|
21
|
+
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
23
|
+
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
24
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
25
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
26
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
27
|
+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
28
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
29
|
+
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
30
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
31
|
+
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
32
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
33
|
+
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
34
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
35
|
+
|
36
|
+
require 'risp'
|
37
|
+
require 'optparse'
|
38
|
+
|
39
|
+
Version = Risp.version
|
40
|
+
interactive_p = false
|
41
|
+
|
42
|
+
ARGV.options do |o|
|
43
|
+
o.banner = "Usage: #{o.program_name} [-options] [source-file]"
|
44
|
+
o.on '-i', '--interactive', 'turn on interactive mode' do
|
45
|
+
interactive_p = true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def with_input(file, &block)
|
50
|
+
file ? File.open(file, &block) : yield($stdin)
|
51
|
+
end
|
52
|
+
|
53
|
+
ARGV.parse!
|
54
|
+
|
55
|
+
source = interactive_p ? nil : ARGV.shift
|
56
|
+
interpreter = Risp::Interpreter.new
|
57
|
+
|
58
|
+
with_input(source) do |f|
|
59
|
+
reader = Risp::Reader.new(f)
|
60
|
+
loop do
|
61
|
+
begin
|
62
|
+
if interactive_p
|
63
|
+
print "Risp> "
|
64
|
+
sexp = reader.read
|
65
|
+
break if sexp.serialize == 'QUIT' || reader.eof?
|
66
|
+
r = sexp.evaluate(interpreter)
|
67
|
+
r.print($stdout)
|
68
|
+
puts
|
69
|
+
else
|
70
|
+
break if reader.eof?
|
71
|
+
sexp = reader.read
|
72
|
+
sexp.evaluate(interpreter)
|
73
|
+
end
|
74
|
+
rescue Risp::RuntimeError, Risp::ReadError => ex
|
75
|
+
$stderr.puts ex.message
|
76
|
+
exit 1 unless interactive_p
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/risp.rb
ADDED
@@ -0,0 +1,522 @@
|
|
1
|
+
# Copyright (c) 2010, arikui
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# Redistribution and use in source and binary forms, with or
|
5
|
+
# without modification, are permitted provided that the
|
6
|
+
# following conditions are met:
|
7
|
+
|
8
|
+
# * Redistributions of source code must retain the above
|
9
|
+
# copyright notice, this list of conditions and the
|
10
|
+
# following disclaimer.
|
11
|
+
# * Redistributions in binary form must reproduce the above
|
12
|
+
# copyright notice, this list of conditions and the following
|
13
|
+
# disclaimer in the documentation and/or other materials
|
14
|
+
# provided with the distribution.
|
15
|
+
# * Neither the name of the arikui-risp nor the names of its
|
16
|
+
# contributors may be used to endorse or promote products
|
17
|
+
# derived from this software without specific prior written
|
18
|
+
# permission.
|
19
|
+
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
21
|
+
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
22
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
23
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
24
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
25
|
+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
26
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
27
|
+
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
28
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
29
|
+
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
31
|
+
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
32
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
|
34
|
+
require 'singleton'
|
35
|
+
|
36
|
+
module Risp
|
37
|
+
VERSION = '0.0.1'
|
38
|
+
|
39
|
+
def self.version
|
40
|
+
VERSION
|
41
|
+
end
|
42
|
+
|
43
|
+
class ReadError < RuntimeError ; end
|
44
|
+
|
45
|
+
class Reader
|
46
|
+
def initialize(f, filename = '-', lineno = 1)
|
47
|
+
@f = f
|
48
|
+
@filename = filename
|
49
|
+
@lineno = lineno
|
50
|
+
@read_table = {}
|
51
|
+
DEFAULT_READ_TABLE.each do |char, m|
|
52
|
+
@read_table[char] = method(m)
|
53
|
+
end
|
54
|
+
@read_table.default = method(DEFAULT_READ_TABLE.default)
|
55
|
+
end
|
56
|
+
|
57
|
+
def read
|
58
|
+
get_sexp()
|
59
|
+
end
|
60
|
+
|
61
|
+
def eof?
|
62
|
+
@f.eof?
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def getc
|
68
|
+
c = @f.getc
|
69
|
+
@lineno += 1 if c == "\n"
|
70
|
+
c
|
71
|
+
end
|
72
|
+
|
73
|
+
def error(msg)
|
74
|
+
raise ReadError, "#{@filename}:#{@lineno}: #{msg}"
|
75
|
+
end
|
76
|
+
|
77
|
+
DEFAULT_READ_TABLE = {
|
78
|
+
'(' => :get_list,
|
79
|
+
"'" => :get_quote,
|
80
|
+
'"' => :get_string,
|
81
|
+
}
|
82
|
+
DEFAULT_READ_TABLE.default = :get_number_or_symbol
|
83
|
+
|
84
|
+
def get_sexp
|
85
|
+
while ch = getc()
|
86
|
+
func = @read_table.fetch(ch, nil) and return func.call(ch)
|
87
|
+
/\A[\s\n]/ =~ ch or return @read_table.default.call(ch)
|
88
|
+
end
|
89
|
+
Nil.instance
|
90
|
+
end
|
91
|
+
|
92
|
+
def skip_spaces
|
93
|
+
while ch = getc()
|
94
|
+
break unless /\A[\s\n]/ =~ ch
|
95
|
+
end
|
96
|
+
ch
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_list(lparen)
|
100
|
+
ch = skip_spaces()
|
101
|
+
error "Un-closed list, missing right paren before EOF" unless ch
|
102
|
+
return Nil.instance if ')' == ch
|
103
|
+
@f.ungetc ch
|
104
|
+
top = list = List.new
|
105
|
+
loop do
|
106
|
+
list.car = get_sexp()
|
107
|
+
ch = skip_spaces()
|
108
|
+
error"Un-closed list, missing right paren before EOF" unless ch
|
109
|
+
case ch
|
110
|
+
when ')' then break
|
111
|
+
when '.'
|
112
|
+
list.cdr = get_sexp()
|
113
|
+
ch = skip_spaces()
|
114
|
+
error "Un-closed list, missing right paren before EOF" unless ch
|
115
|
+
error "Not-right paren token follows after dot-pair." unless ch == ')'
|
116
|
+
return top
|
117
|
+
else
|
118
|
+
@f.ungetc ch
|
119
|
+
list.cdr = List.new
|
120
|
+
list = list.cdr
|
121
|
+
end
|
122
|
+
end
|
123
|
+
top
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_quote(q)
|
127
|
+
list = List.new
|
128
|
+
list.car = Symbol.new('quote')
|
129
|
+
list.cdr = List.new
|
130
|
+
list.cdr.car = get_sexp()
|
131
|
+
list
|
132
|
+
end
|
133
|
+
|
134
|
+
ESC_SEQ_TABLE = {
|
135
|
+
'"' => '"',
|
136
|
+
'n' => "\n",
|
137
|
+
't' => "\t",
|
138
|
+
}
|
139
|
+
|
140
|
+
def get_string(dq)
|
141
|
+
buf = ''
|
142
|
+
quoted = false
|
143
|
+
while ch = getc()
|
144
|
+
if quoted
|
145
|
+
seq = ESC_SEQ_TABLE[ch] and buf << seq
|
146
|
+
quoted = false
|
147
|
+
else
|
148
|
+
case ch
|
149
|
+
when '"' then break
|
150
|
+
when '\\' then quoted = true
|
151
|
+
else buf << ch
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
error "Un-closed string, missing later double quote." unless ch
|
156
|
+
String.new(buf)
|
157
|
+
end
|
158
|
+
|
159
|
+
def get_number_or_symbol(char)
|
160
|
+
token = char.dup
|
161
|
+
while ch = getc()
|
162
|
+
case ch
|
163
|
+
when /\A[\s\n]/ then break
|
164
|
+
when '(', ')'
|
165
|
+
@f.ungetc ch
|
166
|
+
break
|
167
|
+
end
|
168
|
+
token << ch
|
169
|
+
end
|
170
|
+
case token
|
171
|
+
when /\A\d+\Z/ then Integer.new(Kernel.Integer(token))
|
172
|
+
when 't' then T.instance
|
173
|
+
when 'nil' then Nil.instance
|
174
|
+
else Symbol.new(token)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
module Sexp
|
180
|
+
def function?
|
181
|
+
false
|
182
|
+
end
|
183
|
+
|
184
|
+
def symbol?
|
185
|
+
false
|
186
|
+
end
|
187
|
+
|
188
|
+
def atom?
|
189
|
+
false
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
class Cell
|
194
|
+
include Sexp
|
195
|
+
|
196
|
+
def initialize
|
197
|
+
self.car = Nil.instance
|
198
|
+
self.cdr = Nil.instance
|
199
|
+
end
|
200
|
+
|
201
|
+
attr_accessor :car
|
202
|
+
attr_accessor :cdr
|
203
|
+
end
|
204
|
+
|
205
|
+
class List < Cell
|
206
|
+
def print(f)
|
207
|
+
f.print '('
|
208
|
+
print_inner(f, [self])
|
209
|
+
f.print ')'
|
210
|
+
end
|
211
|
+
|
212
|
+
def print_inner(f, appreaed_list)
|
213
|
+
car.print(f)
|
214
|
+
case cdr
|
215
|
+
when Nil
|
216
|
+
;
|
217
|
+
when List
|
218
|
+
f.print ' '
|
219
|
+
if appreaed_list.any?{|x| x.equal?(cdr) }
|
220
|
+
f.print '...'
|
221
|
+
else
|
222
|
+
appreaed_list << cdr
|
223
|
+
cdr.print_inner(f, appreaed_list)
|
224
|
+
end
|
225
|
+
else
|
226
|
+
f.print ' . '
|
227
|
+
cdr.print(f)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def serialize
|
232
|
+
inspect
|
233
|
+
end
|
234
|
+
|
235
|
+
def evaluate(intp)
|
236
|
+
operator = car.evaluate(intp)
|
237
|
+
intp.apply(operator, cdr)
|
238
|
+
end
|
239
|
+
|
240
|
+
def to_a
|
241
|
+
ret = []
|
242
|
+
to_a_main ret, self
|
243
|
+
ret
|
244
|
+
end
|
245
|
+
|
246
|
+
def to_a_main(buf, lst)
|
247
|
+
unless lst == Nil.instance
|
248
|
+
buf << lst.car
|
249
|
+
to_a_main(buf, lst.cdr)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def length
|
254
|
+
length_main self
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
|
259
|
+
def length_main(lst)
|
260
|
+
lst == Nil.instance ? 0 : 1 + length_main(lst.cdr)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
class Nil
|
265
|
+
include Sexp
|
266
|
+
include Singleton
|
267
|
+
|
268
|
+
def print(f)
|
269
|
+
f.print "NIL"
|
270
|
+
end
|
271
|
+
|
272
|
+
def serialize
|
273
|
+
"NIL"
|
274
|
+
end
|
275
|
+
|
276
|
+
def evaluate(intp)
|
277
|
+
self
|
278
|
+
end
|
279
|
+
|
280
|
+
def to_a
|
281
|
+
[]
|
282
|
+
end
|
283
|
+
|
284
|
+
def atom?
|
285
|
+
true
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
class Atom
|
290
|
+
include Sexp
|
291
|
+
|
292
|
+
def atom?
|
293
|
+
true
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
class T < Atom
|
298
|
+
include Singleton
|
299
|
+
|
300
|
+
def print(f)
|
301
|
+
f.print "T"
|
302
|
+
end
|
303
|
+
|
304
|
+
def serialize
|
305
|
+
"T"
|
306
|
+
end
|
307
|
+
|
308
|
+
def evaluate(intp)
|
309
|
+
self
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class Symbol < Atom
|
314
|
+
CACHE = {}
|
315
|
+
|
316
|
+
def self.new(name)
|
317
|
+
CACHE[name] ||= super(name)
|
318
|
+
end
|
319
|
+
|
320
|
+
def initialize(name)
|
321
|
+
@name = name
|
322
|
+
end
|
323
|
+
|
324
|
+
attr_reader :name
|
325
|
+
|
326
|
+
def print(f)
|
327
|
+
f.print serialize()
|
328
|
+
end
|
329
|
+
|
330
|
+
def serialize
|
331
|
+
name.upcase
|
332
|
+
end
|
333
|
+
|
334
|
+
def evaluate(intp)
|
335
|
+
intp.symbol_value(self)
|
336
|
+
end
|
337
|
+
|
338
|
+
def symbol?
|
339
|
+
true
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
class Number < Atom
|
344
|
+
end
|
345
|
+
|
346
|
+
class Integer < Number
|
347
|
+
def initialize(n)
|
348
|
+
@value = n
|
349
|
+
end
|
350
|
+
|
351
|
+
attr_reader :value
|
352
|
+
|
353
|
+
def print(f)
|
354
|
+
f.print serialize()
|
355
|
+
end
|
356
|
+
|
357
|
+
def serialize
|
358
|
+
value.to_s
|
359
|
+
end
|
360
|
+
|
361
|
+
def evaluate(intp)
|
362
|
+
self
|
363
|
+
end
|
364
|
+
|
365
|
+
def +(other)
|
366
|
+
Integer.new(self.value + other.value)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
class String < Atom
|
371
|
+
def initialize(value)
|
372
|
+
@value = value
|
373
|
+
end
|
374
|
+
|
375
|
+
attr_reader :value
|
376
|
+
|
377
|
+
def print(f)
|
378
|
+
f.print serialize()
|
379
|
+
end
|
380
|
+
|
381
|
+
def serialize
|
382
|
+
value
|
383
|
+
end
|
384
|
+
|
385
|
+
def evaluate(intp)
|
386
|
+
self
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
class NativeFunction < Atom
|
391
|
+
def function?
|
392
|
+
true
|
393
|
+
end
|
394
|
+
|
395
|
+
def initialize(arity = nil, &body)
|
396
|
+
@arity = arity
|
397
|
+
@body = body
|
398
|
+
end
|
399
|
+
|
400
|
+
def print(f)
|
401
|
+
f.print serialize
|
402
|
+
end
|
403
|
+
|
404
|
+
def serialize
|
405
|
+
"#{inspect.split(/\s+/)[0]}>"
|
406
|
+
end
|
407
|
+
|
408
|
+
def call(intp, args)
|
409
|
+
args = args.to_a
|
410
|
+
if @arity && @arity != args.size
|
411
|
+
raise RuntimeError, "wrong number of arguments (#{args.size} for #{@arity})"
|
412
|
+
end
|
413
|
+
@body.call(*args)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
class Function < Atom
|
418
|
+
def function?
|
419
|
+
true
|
420
|
+
end
|
421
|
+
|
422
|
+
def initialize(params, body)
|
423
|
+
@params = params.to_a
|
424
|
+
@body = body
|
425
|
+
end
|
426
|
+
|
427
|
+
def print(f)
|
428
|
+
f.print serialize
|
429
|
+
end
|
430
|
+
|
431
|
+
def serialize
|
432
|
+
"#{inspect.split(/\s+/)[0]}>"
|
433
|
+
end
|
434
|
+
|
435
|
+
def call(intp, args)
|
436
|
+
args = args.to_a
|
437
|
+
unless @params.size == args.size
|
438
|
+
raise RuntimeError, "wrong number of arguments (#{args.size} for #{@params.size})"
|
439
|
+
end
|
440
|
+
ret = nil
|
441
|
+
intp.local_environment(@params.zip(args)) do
|
442
|
+
@body.each{|s| ret = s.evaluate(intp) }
|
443
|
+
end
|
444
|
+
ret
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
class RuntimeError < ::RuntimeError ; end
|
449
|
+
|
450
|
+
class Interpreter
|
451
|
+
def initialize
|
452
|
+
@specials = {}
|
453
|
+
@frames = []
|
454
|
+
@specials[Symbol.new('car')] = NativeFunction.new(1){|list|
|
455
|
+
list.evaluate(self).car
|
456
|
+
}
|
457
|
+
@specials[Symbol.new('cdr')] = NativeFunction.new(1){|list|
|
458
|
+
list.evaluate(self).cdr
|
459
|
+
}
|
460
|
+
@specials[Symbol.new('cons')] = NativeFunction.new(2){|car, cdr|
|
461
|
+
list = List.new
|
462
|
+
list.car = car.evaluate(self)
|
463
|
+
list.cdr = cdr.evaluate(self)
|
464
|
+
list
|
465
|
+
}
|
466
|
+
@specials[Symbol.new('eq')] = NativeFunction.new(2){|x, y|
|
467
|
+
x.evaluate(self).equal?(y.evaluate(self)) ? T.instance : Nil.instance
|
468
|
+
}
|
469
|
+
@specials[Symbol.new('atom')] = NativeFunction.new(1){|sexp|
|
470
|
+
sexp.evaluate(self).atom? ? T.instance : Nil.instance
|
471
|
+
}
|
472
|
+
@specials[Symbol.new('quote')] = NativeFunction.new(1){|sexp|
|
473
|
+
sexp
|
474
|
+
}
|
475
|
+
@specials[Symbol.new('cond')] = NativeFunction.new{|*forms|
|
476
|
+
result = forms.each{|form|
|
477
|
+
test, *body = form.to_a
|
478
|
+
if test.evaluate(self) == T.instance
|
479
|
+
ret = nil
|
480
|
+
body.each{|s| ret = s.evaluate(self) }
|
481
|
+
break ret
|
482
|
+
end
|
483
|
+
}
|
484
|
+
result.equal?(forms) ? Nil.instance : result
|
485
|
+
}
|
486
|
+
@specials[Symbol.new('define')] = NativeFunction.new(2){|bindee, value|
|
487
|
+
case
|
488
|
+
when bindee.symbol?
|
489
|
+
(@frames.last || @specials)[bindee] = value.evaluate(self)
|
490
|
+
else
|
491
|
+
raise RuntimeError, "#{bindee.serialize} is not a symbol."
|
492
|
+
end
|
493
|
+
}
|
494
|
+
@specials[Symbol.new('lambda')] = NativeFunction.new{|params, *body|
|
495
|
+
Function.new(params, body)
|
496
|
+
}
|
497
|
+
@specials[Symbol.new('+')] = NativeFunction.new{|*args|
|
498
|
+
args.map{|arg| arg.evaluate(self) }.inject(Integer.new(0), :+)
|
499
|
+
}
|
500
|
+
end
|
501
|
+
|
502
|
+
def local_environment(alist)
|
503
|
+
f = alist.each_with_object({}){|(sym, val), h| h[sym] = val }
|
504
|
+
@frames.push f
|
505
|
+
ret = yield
|
506
|
+
@frames.pop
|
507
|
+
ret
|
508
|
+
end
|
509
|
+
|
510
|
+
def symbol_value(sym)
|
511
|
+
@frames.reverse_each do |frame|
|
512
|
+
v = frame[sym] and return v
|
513
|
+
end
|
514
|
+
@specials[sym] or raise RuntimeError, "unbound variable - `#{sym.name}'"
|
515
|
+
end
|
516
|
+
|
517
|
+
def apply(function, args)
|
518
|
+
raise RuntimeError, "not a function - #{function}" unless function.function?
|
519
|
+
function.call(self, args)
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'expectations'
|
2
|
+
require 'stringio'
|
3
|
+
require 'risp'
|
4
|
+
|
5
|
+
Expectations do
|
6
|
+
expect Risp::Integer do
|
7
|
+
Risp::Reader.new(StringIO.new("123")).read
|
8
|
+
end
|
9
|
+
|
10
|
+
expect 123 do
|
11
|
+
Risp::Reader.new(StringIO.new("123")).read.value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
__END__
|
17
|
+
require 'test/unit'
|
18
|
+
require 'stringio'
|
19
|
+
|
20
|
+
class TestRispReader < Test::Unit::TestCase
|
21
|
+
def test_integer
|
22
|
+
sexp = Risp::Reader.new(StringIO.new("123")).read
|
23
|
+
assert_kind_of Risp::Integer, sexp
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: risp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- arikui
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-11-19 00:00:00 +09:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: A toy lisp interpreter.
|
23
|
+
email: arikui.ruby@gmail.com
|
24
|
+
executables:
|
25
|
+
- risp
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README
|
30
|
+
files:
|
31
|
+
- README
|
32
|
+
- Rakefile
|
33
|
+
- bin/risp
|
34
|
+
- test/test_risp_reader.rb
|
35
|
+
- lib/risp.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://wiki.github.com/arikui1911/risp
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --title
|
43
|
+
- risp documentation
|
44
|
+
- --charset
|
45
|
+
- utf-8
|
46
|
+
- --opname
|
47
|
+
- index.html
|
48
|
+
- --line-numbers
|
49
|
+
- --main
|
50
|
+
- README
|
51
|
+
- --inline-source
|
52
|
+
- --exclude
|
53
|
+
- ^(examples|extras)/
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project: risp
|
77
|
+
rubygems_version: 1.3.7
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: A toy lisp interpreter; provides `pure-lisp' functions and Lisp-1 namespaces. Currently it lacks package system and macro(!!).
|
81
|
+
test_files:
|
82
|
+
- test/test_risp_reader.rb
|