rbl 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +103 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/rbl +27 -0
- data/bin/setup +8 -0
- data/lib/rubylisp/environment.rb +75 -0
- data/lib/rubylisp/evaluator.rb +227 -0
- data/lib/rubylisp/parser.rb +28 -0
- data/lib/rubylisp/printer.rb +33 -0
- data/lib/rubylisp/reader.rb +209 -0
- data/lib/rubylisp/repl.rb +26 -0
- data/lib/rubylisp/types.rb +169 -0
- data/lib/rubylisp/version.rb +3 -0
- data/lib/rubylisp.rb +1 -0
- data/rubylisp/core.rbl +245 -0
- data/rubylisp/repl.rbl +2 -0
- data/rubylisp.gemspec +34 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 79590c140a9864fb5f46c288fe46603d8b63b3c9
|
4
|
+
data.tar.gz: 2109a3b23bf7b20e8e30e95d8e0451119ec72a5a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c66238bc2c4772dd3557edfd11a56309135e9f423361a8c9783b14193a75c7d1208da3005463d2fc5682e34714ac88ec748bcd3c61efb41611f24f6a12b9372c
|
7
|
+
data.tar.gz: 82e872d0e9e44745b075f5b2a26bf9fdebfcc6666fd7ac45c4cac2c13df81333e98952998a4b1e250b4e565e0ef8da3b60616364c292977fed10594fedf7b7d5
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Dave Yarwood
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# RubyLisp
|
2
|
+
|
3
|
+
A Lisp dialect of Ruby.
|
4
|
+
|
5
|
+
## Why?
|
6
|
+
|
7
|
+
Lisps are great, but I haven't found one usable enough (by my own standards) for scripting. [Clojure][clojure] is my favorite language from a design perpective, and the fact that you can leverage existing JVM libraries is super convenient. But there's just one problem -- its startup time is _so slow_.
|
8
|
+
|
9
|
+
[Common Lisp][commonlisp] is nice; it's a powerful Lisp, and it's fast. But it's not always
|
10
|
+
easy to find X existing library to do Y thing, it's maybe a little too
|
11
|
+
low-level for my liking, and the tooling situation is not so great.
|
12
|
+
|
13
|
+
[Ruby][ruby] is great for scripting, cross-platform, and has hella good libraries, but there's just one problem -- it's not a Lisp.
|
14
|
+
|
15
|
+
I've played around with the [make-a-lisp][mal] guide a few times in the past to
|
16
|
+
build Lisps for fun in languages like Rust. I thought it would be interesting to
|
17
|
+
try and build for myself the convenient scripting Lisp that I always wanted. It turns out that it's super easy to write a Lisp interpreter, but it's awful time-consuming to build a whole language from scratch. I had a shower thought that the Ruby standard library can already do about 80% of the things a faithful Lisp should do, so maybe the path of least resistance to building the Lisp of my dreams is to write the interpreter in Ruby and totally cheat on all of the function implementations by delegating to functions and types that have already been implemented in the Ruby stdlib.
|
18
|
+
|
19
|
+
I think I was right. Check it out, everyone -- you can write your Ruby scripts in Lisp now!
|
20
|
+
|
21
|
+
## Features
|
22
|
+
|
23
|
+
> NOTE: RubyLisp is still in early development. If you find something is broken
|
24
|
+
> or missing, please file an issue, or better yet, fork this repo, add/fix it
|
25
|
+
> yourself, and make a Pull Request!
|
26
|
+
|
27
|
+
* Syntax and stdlib functions heavily influenced by Clojure.
|
28
|
+
* Easy, convenient Ruby inter-op.
|
29
|
+
* Fire up a REPL or run it in a script.
|
30
|
+
* Immutable linked lists, vectors, and hashes courtesy of the [Hamster][hamster]
|
31
|
+
library.
|
32
|
+
* _(TODO)_ Macros, Clojure-style.
|
33
|
+
* _(TODO)_ Namespaces, Clojure-style.
|
34
|
+
* _(TODO)_ Dependency management / the ability to use some sort of build tool to
|
35
|
+
include Ruby libraries and use them via inter-op.
|
36
|
+
|
37
|
+
## Examples
|
38
|
+
|
39
|
+
_TODO_
|
40
|
+
|
41
|
+
## Installation
|
42
|
+
|
43
|
+
Run this:
|
44
|
+
|
45
|
+
```bash
|
46
|
+
$ gem install rubylisp
|
47
|
+
```
|
48
|
+
|
49
|
+
## Usage
|
50
|
+
|
51
|
+
To start a REPL:
|
52
|
+
|
53
|
+
```bash
|
54
|
+
$ rbl
|
55
|
+
```
|
56
|
+
|
57
|
+
To interpret a file containing RubyLisp code:
|
58
|
+
|
59
|
+
```bash
|
60
|
+
$ rbl my_sweet_rubylisp_script.rbl
|
61
|
+
```
|
62
|
+
|
63
|
+
Or, if you'd like, you can include a shebang, make the script executable and run it directly:
|
64
|
+
|
65
|
+
```
|
66
|
+
$ cat << EOF > reticulate_splines.rbl
|
67
|
+
#!/usr/bin/env rbl
|
68
|
+
(print "Reticulating splines... ")
|
69
|
+
(Kernel::sleep 2)
|
70
|
+
(println "done.")
|
71
|
+
EOF
|
72
|
+
|
73
|
+
$ chmod +x reticulate_splines.rbl
|
74
|
+
|
75
|
+
$ ./reticulate_splines.rbl
|
76
|
+
Reticulating splines... done.
|
77
|
+
```
|
78
|
+
|
79
|
+
## Development
|
80
|
+
|
81
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
82
|
+
|
83
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
84
|
+
|
85
|
+
## Contributing
|
86
|
+
|
87
|
+
Contributions welcome!
|
88
|
+
|
89
|
+
As a general rule, I want RubyLisp to mirror the behavior of Clojure as closely
|
90
|
+
as possible. If you have a favorite Clojure function/macro and it isn't included
|
91
|
+
in RubyLisp yet, why not add it yourself and make a Pull Request? :)
|
92
|
+
|
93
|
+
## License
|
94
|
+
|
95
|
+
Copyright © 2017 Dave Yarwood
|
96
|
+
|
97
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
98
|
+
|
99
|
+
[clojure]: https://clojure.org
|
100
|
+
[commonlisp]: https://en.wikipedia.org/wiki/Common_Lisp
|
101
|
+
[ruby]: http://ruby-lang.org
|
102
|
+
[mal]: https://github.com/kanaka/mal
|
103
|
+
[hamster]: https://github.com/hamstergem/hamster
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rubylisp"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/rbl
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'rubylisp/parser'
|
5
|
+
require 'rubylisp/repl'
|
6
|
+
|
7
|
+
USAGE = <<~HEREDOC
|
8
|
+
Usage:
|
9
|
+
To start a REPL session:
|
10
|
+
#{File.basename $0}
|
11
|
+
|
12
|
+
To run a file:
|
13
|
+
#{File.basename $0} <filename>
|
14
|
+
HEREDOC
|
15
|
+
|
16
|
+
if __FILE__ == $0
|
17
|
+
case ARGV.length
|
18
|
+
when 0
|
19
|
+
RubyLisp::REPL::start
|
20
|
+
when 1
|
21
|
+
file_contents = File.read ARGV[0]
|
22
|
+
input = file_contents.gsub(/\A#!.*\n/, '')
|
23
|
+
puts RubyLisp::Parser.parse(input)
|
24
|
+
else
|
25
|
+
puts USAGE
|
26
|
+
end
|
27
|
+
end
|
data/bin/setup
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'rubylisp/parser'
|
2
|
+
|
3
|
+
module RubyLisp
|
4
|
+
class Environment
|
5
|
+
attr_accessor :namespace, :vars, :outer
|
6
|
+
|
7
|
+
def initialize(outer: nil, namespace: nil)
|
8
|
+
@vars = {'*ns*' => (outer or self)}
|
9
|
+
@outer = outer
|
10
|
+
@namespace = namespace or
|
11
|
+
(outer.namespace if outer) or
|
12
|
+
"__ns_#{rand 10000}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def set key, val
|
16
|
+
@vars[key] = val
|
17
|
+
end
|
18
|
+
|
19
|
+
def find key
|
20
|
+
if @vars.member? key
|
21
|
+
self
|
22
|
+
elsif @outer
|
23
|
+
@outer.find key
|
24
|
+
else
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def get key
|
30
|
+
env = find key
|
31
|
+
if env
|
32
|
+
env.vars[key]
|
33
|
+
else
|
34
|
+
raise RubyLisp::RuntimeError, "Unable to resolve symbol: #{key}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Copies all vars from `other_env` to this one.
|
39
|
+
def refer other_env
|
40
|
+
@vars = @vars.merge other_env.vars do |key, oldval, newval|
|
41
|
+
if key == '*ns*'
|
42
|
+
oldval
|
43
|
+
else
|
44
|
+
puts "WARNING: #{namespace}/#{key} being replaced by " +
|
45
|
+
"#{other_env.namespace}/#{key}"
|
46
|
+
newval
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# TODO: some notion of "required namespaces" whose vars can be accessed when
|
52
|
+
# qualified with the namespace name
|
53
|
+
|
54
|
+
def load_rbl_file path
|
55
|
+
root = File.expand_path '../..', File.dirname(__FILE__)
|
56
|
+
input = File.read "#{root}/#{path}"
|
57
|
+
|
58
|
+
namespace = RubyLisp::Environment.new
|
59
|
+
RubyLisp::Parser.parse input, namespace
|
60
|
+
namespace
|
61
|
+
end
|
62
|
+
|
63
|
+
def stdlib
|
64
|
+
namespace = load_rbl_file 'rubylisp/core.rbl'
|
65
|
+
refer namespace
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def repl
|
70
|
+
namespace = load_rbl_file 'rubylisp/repl.rbl'
|
71
|
+
refer namespace
|
72
|
+
self
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'rubylisp/environment'
|
2
|
+
require 'rubylisp/printer'
|
3
|
+
require 'rubylisp/types'
|
4
|
+
|
5
|
+
module RubyLisp
|
6
|
+
module Evaluator
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def assert_number_of_args sexp, num_args
|
10
|
+
fn, *args = sexp.value
|
11
|
+
fn_name = fn.value
|
12
|
+
unless args.count == num_args
|
13
|
+
raise RubyLisp::RuntimeError,
|
14
|
+
"Wrong number of arguments passed to `#{fn_name}`; " +
|
15
|
+
"got #{args.count}, expected #{num_args}."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def assert_at_least_n_args sexp, num_args
|
20
|
+
fn, *args = sexp.value
|
21
|
+
fn_name = fn.value
|
22
|
+
unless args.count >= num_args
|
23
|
+
raise RubyLisp::RuntimeError,
|
24
|
+
"Wrong number of arguments passed to `#{fn_name}`; " +
|
25
|
+
"got #{args.count}, expected at least #{num_args}."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def assert_arg_type sexp, arg_number, arg_type
|
30
|
+
fn = sexp.value[0]
|
31
|
+
fn_name = fn.value
|
32
|
+
arg = if arg_number == 'last'
|
33
|
+
sexp.value.last
|
34
|
+
else
|
35
|
+
sexp.value[arg_number]
|
36
|
+
end
|
37
|
+
|
38
|
+
arg_description = if arg_number == 'last'
|
39
|
+
'The last argument'
|
40
|
+
else
|
41
|
+
"Argument ##{arg_number}"
|
42
|
+
end
|
43
|
+
|
44
|
+
arg_types = if arg_type.class == Array
|
45
|
+
arg_type
|
46
|
+
else
|
47
|
+
[arg_type]
|
48
|
+
end
|
49
|
+
|
50
|
+
expected = case arg_types.count
|
51
|
+
when 1
|
52
|
+
arg_types.first
|
53
|
+
when 2
|
54
|
+
arg_types.join(' or ')
|
55
|
+
else
|
56
|
+
last_arg_type = arg_types.pop
|
57
|
+
arg_types.join(', ') + " or #{last_arg_type}"
|
58
|
+
end
|
59
|
+
|
60
|
+
if arg_types.none? {|type| arg.is_a? type}
|
61
|
+
raise RubyLisp::RuntimeError,
|
62
|
+
"#{arg_description} to `#{fn_name}` must be a " +
|
63
|
+
"#{expected}; got a #{arg.class}."
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def eval_ast input, env
|
68
|
+
case input
|
69
|
+
when Array # of ASTs to be evaluated, e.g. multiple expressions in a file
|
70
|
+
input.compact.map {|form| eval_ast(form, env)}.last
|
71
|
+
when RubyLisp::HashMap
|
72
|
+
input.value.map {|k, v| [eval_ast(k, env), eval_ast(v, env)]}
|
73
|
+
when RubyLisp::List
|
74
|
+
if input.value.empty?
|
75
|
+
input.value
|
76
|
+
elsif input.value[0].value == 'apply'
|
77
|
+
coll = eval_ast(input.value[-1], env) || Hamster::Vector[]
|
78
|
+
|
79
|
+
# evaluate the last argument
|
80
|
+
sexp = RubyLisp::List.new(input.value.fill(coll, -1, 1))
|
81
|
+
# assert that it's enumerable
|
82
|
+
assert_arg_type sexp, 'last', Enumerable
|
83
|
+
|
84
|
+
# drop `apply` and pull the last form's contents into the sexp as
|
85
|
+
# multiple arguments
|
86
|
+
all_but_last_arg = sexp.value[1..-2].map {|arg| eval_ast arg, env}
|
87
|
+
fn, *args = all_but_last_arg + coll.to_list
|
88
|
+
sexp = RubyLisp::List.new([RubyLisp::Symbol.new("apply"), fn])
|
89
|
+
assert_arg_type sexp, 1, Proc
|
90
|
+
fn.call(*args)
|
91
|
+
elsif input.value[0].value == 'def'
|
92
|
+
assert_arg_type input, 1, RubyLisp::Symbol
|
93
|
+
key, val = input.value[1..-1]
|
94
|
+
env.set key.value, eval_ast(val, env)
|
95
|
+
elsif input.value[0].value == 'do'
|
96
|
+
body = input.value[1..-1]
|
97
|
+
body.map {|form| eval_ast(form, env)}.last
|
98
|
+
elsif input.value[0].value == 'fn'
|
99
|
+
if input.value[1].class == RubyLisp::Symbol
|
100
|
+
fn_name = input.value[1].value
|
101
|
+
bindings, *body = input.value[2..-1]
|
102
|
+
else
|
103
|
+
fn_name = "__fn_#{rand 10000}"
|
104
|
+
bindings, *body = input.value[1..-1]
|
105
|
+
end
|
106
|
+
|
107
|
+
unless bindings.class == RubyLisp::Vector
|
108
|
+
raise RubyLisp::RuntimeError,
|
109
|
+
"The bindings of `fn` must be a RubyLisp::Vector."
|
110
|
+
end
|
111
|
+
|
112
|
+
ampersand_indices = bindings.value.to_list.indices {|x| x.value == '&'}
|
113
|
+
if ampersand_indices.any? {|i| i != bindings.value.count - 2}
|
114
|
+
raise RubyLisp::RuntimeError,
|
115
|
+
"An '&' can only occur right before the last binding."
|
116
|
+
end
|
117
|
+
|
118
|
+
fn = lambda do |*args|
|
119
|
+
inner_env = RubyLisp::Environment.new(outer: env)
|
120
|
+
inner_env.set(fn_name, fn) # self-referential lambda omg
|
121
|
+
|
122
|
+
sexp = RubyLisp::List.new([RubyLisp::Symbol.new(fn_name), *args])
|
123
|
+
if bindings.value.any? {|binding| binding.value == '&'}
|
124
|
+
required_args = bindings.value.count - 2
|
125
|
+
assert_at_least_n_args sexp, required_args
|
126
|
+
|
127
|
+
bindings.value[0..-3].zip(args.take(required_args)).each do |k, v|
|
128
|
+
inner_env.set(k.value, v)
|
129
|
+
end
|
130
|
+
|
131
|
+
rest_args = if args.count > required_args
|
132
|
+
args[required_args..-1].to_list
|
133
|
+
else
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
|
137
|
+
inner_env.set(bindings.value[-1].value, rest_args)
|
138
|
+
else
|
139
|
+
required_args = bindings.value.count
|
140
|
+
assert_number_of_args sexp, required_args
|
141
|
+
|
142
|
+
bindings.value.zip(args).each do |k, v|
|
143
|
+
inner_env.set(k.value, v)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
body.map {|form| eval_ast(form, inner_env)}.last
|
148
|
+
end
|
149
|
+
elsif input.value[0].value == 'if'
|
150
|
+
cond, then_form, else_form = input.value[1..-1]
|
151
|
+
unless then_form
|
152
|
+
raise RubyLisp::RuntimeError,
|
153
|
+
"An `if` form must at least have a 'then' branch."
|
154
|
+
end
|
155
|
+
|
156
|
+
if (eval_ast cond, env)
|
157
|
+
eval_ast(then_form, env)
|
158
|
+
elsif else_form
|
159
|
+
eval_ast(else_form, env)
|
160
|
+
end
|
161
|
+
elsif input.value[0].value == 'in-ns'
|
162
|
+
assert_number_of_args input, 1
|
163
|
+
input.value[1] = eval_ast(input.value[1], env)
|
164
|
+
assert_arg_type input, 1, RubyLisp::Symbol
|
165
|
+
ns_name = input.value[1].to_s
|
166
|
+
# TODO: register ns and switch to it
|
167
|
+
env.namespace = ns_name
|
168
|
+
elsif input.value[0].value == 'let'
|
169
|
+
assert_arg_type input, 1, RubyLisp::Vector
|
170
|
+
inner_env = RubyLisp::Environment.new(outer: env)
|
171
|
+
bindings, *body = input.value[1..-1]
|
172
|
+
unless bindings.value.count.even?
|
173
|
+
raise RubyLisp::RuntimeError,
|
174
|
+
"The bindings vector of `let` must contain an even number " +
|
175
|
+
" of forms."
|
176
|
+
end
|
177
|
+
|
178
|
+
bindings.value.each_slice(2) do |(k, v)|
|
179
|
+
inner_env.set k.value, eval_ast(v, inner_env)
|
180
|
+
end
|
181
|
+
|
182
|
+
body.map {|form| eval_ast(form, inner_env)}.last
|
183
|
+
elsif input.value[0].value == 'ns'
|
184
|
+
# ns will be defined more robustly in rbl.core, but rbl.core also
|
185
|
+
# needs the `ns` form in order to declare that it is rbl.core.
|
186
|
+
#
|
187
|
+
# defining it here in a minimal form where it is equivalent to in-ns,
|
188
|
+
# except that you don't have to quote the namespace name
|
189
|
+
assert_number_of_args input, 1
|
190
|
+
assert_arg_type input, 1, RubyLisp::Symbol
|
191
|
+
ns_name = input.value[1].to_s
|
192
|
+
# TODO: register ns and switch to it
|
193
|
+
env.namespace = ns_name
|
194
|
+
elsif input.value[0].value == 'quote'
|
195
|
+
assert_number_of_args input, 1
|
196
|
+
fn, *args = input.value
|
197
|
+
quoted_form = args[0]
|
198
|
+
|
199
|
+
if [RubyLisp::List, RubyLisp::Vector].member? quoted_form.class
|
200
|
+
quoted_form.quote.value
|
201
|
+
else
|
202
|
+
quoted_form.quote
|
203
|
+
end
|
204
|
+
elsif input.value[0].value == 'resolve'
|
205
|
+
assert_number_of_args input, 1
|
206
|
+
symbol = eval_ast(input.value[1], env)
|
207
|
+
sexp = RubyLisp::List.new(input.value.fill(symbol, 1, 1))
|
208
|
+
assert_arg_type sexp, 1, RubyLisp::Symbol
|
209
|
+
symbol.resolve(env)
|
210
|
+
else
|
211
|
+
fn, *args = input.value.map {|value| eval_ast(value, env)}
|
212
|
+
fn.call(*args)
|
213
|
+
end
|
214
|
+
when RubyLisp::Symbol
|
215
|
+
if input.quoted
|
216
|
+
input
|
217
|
+
else
|
218
|
+
input.resolve(env)
|
219
|
+
end
|
220
|
+
when RubyLisp::Vector
|
221
|
+
input.value.map {|value| eval_ast(value, env)}
|
222
|
+
else
|
223
|
+
input.value
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubylisp/printer'
|
2
|
+
require 'rubylisp/reader'
|
3
|
+
require 'rubylisp/environment'
|
4
|
+
require 'rubylisp/evaluator'
|
5
|
+
|
6
|
+
module RubyLisp
|
7
|
+
module Parser
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def read input
|
11
|
+
RubyLisp::Reader.read_str input
|
12
|
+
end
|
13
|
+
|
14
|
+
def eval_ast input, env
|
15
|
+
RubyLisp::Evaluator.eval_ast input, env
|
16
|
+
end
|
17
|
+
|
18
|
+
def print input
|
19
|
+
RubyLisp::Printer.pr_str input
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse input, env = RubyLisp::Environment.new.stdlib
|
23
|
+
ast = read input
|
24
|
+
result = eval_ast ast, env
|
25
|
+
print result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rubylisp/types'
|
2
|
+
|
3
|
+
module RubyLisp
|
4
|
+
module Printer
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def pr_str(*xs)
|
8
|
+
if xs.count > 1
|
9
|
+
xs.map {|x| pr_str(x)}.join(' ')
|
10
|
+
else
|
11
|
+
x = xs.first
|
12
|
+
case x
|
13
|
+
when Array, Hamster::Vector
|
14
|
+
"[#{x.map {|item| pr_str(item)}.join(' ')}]"
|
15
|
+
when Hash
|
16
|
+
"{#{x.map {|k, v| "#{pr_str(k)} #{pr_str(v)}"}.join ", "}}"
|
17
|
+
when Hamster::Hash
|
18
|
+
pr_str x.to_hash
|
19
|
+
when Hamster::List
|
20
|
+
"(#{x.map {|item| pr_str(item)}.join(' ')})"
|
21
|
+
when RubyLisp::Keyword
|
22
|
+
":#{x.value}"
|
23
|
+
when RubyLisp::List
|
24
|
+
"(#{x.value.map {|item| pr_str(item)}.join(' ')})"
|
25
|
+
when RubyLisp::Symbol, RubyLisp::Int, RubyLisp::Float
|
26
|
+
x.value
|
27
|
+
else
|
28
|
+
x.inspect
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|