bus-scheme 0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +25 -0
- data/Manifest.txt +14 -0
- data/README.txt +63 -0
- data/Rakefile +25 -0
- data/bin/bus +19 -0
- data/lib/array_extensions.rb +19 -0
- data/lib/bus_scheme.rb +89 -0
- data/lib/eval.rb +51 -0
- data/lib/object_extensions.rb +8 -0
- data/lib/parser.rb +69 -0
- data/test/foo.scm +1 -0
- data/test/test_eval.rb +158 -0
- data/test/test_helper.rb +10 -0
- data/test/test_parser.rb +122 -0
- metadata +78 -0
data/COPYING
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2007, Phil Hagelberg
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
* Neither the name of Phil Hagelberg nor the names of its contributors
|
13
|
+
may be used to endorse or promote products derived from this software
|
14
|
+
without specific prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
Bus Scheme
|
2
|
+
by Phil Hagelberg (c) 2007
|
3
|
+
http://bus-scheme.rubyforge.org
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
Bus Scheme is a Scheme written in Ruby, but implemented on the bus!
|
8
|
+
Every programmer must implement Scheme as a rite of passage; this is
|
9
|
+
mine. Note that all the implementation of Bus Scheme must be written
|
10
|
+
while on a bus. Documentation, tests, and administrivia may be
|
11
|
+
accomplished elsewhere, but all actual implementation code is strictly
|
12
|
+
bus-driven. Patches are welcome as long as they were written while
|
13
|
+
riding a bus. (If your daily commute does not involve a bus but you
|
14
|
+
want to submit a patch, we may be able to work something out regarding
|
15
|
+
code written on trains, ferries, or perhaps even carpool lanes.) Bus
|
16
|
+
Scheme is primarily a toy; using it for anything serious is (right
|
17
|
+
now) ill-advised.
|
18
|
+
|
19
|
+
Bus Scheme aims for general Scheme usefulness optimized for learning
|
20
|
+
and fun. It's not targeting R5RS or anything like that.
|
21
|
+
|
22
|
+
== Install
|
23
|
+
|
24
|
+
* sudo gem install bus-scheme
|
25
|
+
|
26
|
+
For the source:
|
27
|
+
|
28
|
+
* git clone git://git.caboo.se/bus_scheme.git
|
29
|
+
|
30
|
+
== Usage
|
31
|
+
|
32
|
+
$ bus # drop into a repl
|
33
|
+
|
34
|
+
$ bus -e "(do some stuff)"
|
35
|
+
|
36
|
+
$ bus foo.scm # load a file -- todo
|
37
|
+
|
38
|
+
== Todo
|
39
|
+
|
40
|
+
Bus Scheme is currently missing huge pieces of functionality:
|
41
|
+
|
42
|
+
* lexical scoping
|
43
|
+
* closures
|
44
|
+
* loading files
|
45
|
+
* eval input from command line, stdin
|
46
|
+
* parse cons cells
|
47
|
+
* parse character literals
|
48
|
+
* numeric tower?
|
49
|
+
|
50
|
+
Failing tests for most of these are already included (commented out,
|
51
|
+
mostly) in the relevant test files.
|
52
|
+
|
53
|
+
== Requirements
|
54
|
+
|
55
|
+
Bus Scheme should run on (at least) Ruby 1.8, Ruby 1.9, and Rubinius.
|
56
|
+
|
57
|
+
== Bonus Fact
|
58
|
+
|
59
|
+
I haven't actually used a real Scheme yet. Everything I know about it
|
60
|
+
I've gathered from reading The Little Schemer, watching the Structure
|
61
|
+
and Interpretation of Computer Programs videos, and reading lots about
|
62
|
+
Common Lisp and Emacs Lisp. If there are huge gaping flaws in the
|
63
|
+
implementation, this is likely to be why.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require './lib/bus_scheme.rb'
|
6
|
+
require 'rake/testtask'
|
7
|
+
|
8
|
+
Hoe.new('bus-scheme', BusScheme::VERSION) do |p|
|
9
|
+
p.rubyforge_name = 'bus-scheme'
|
10
|
+
p.author = 'Phil Hagelberg'
|
11
|
+
p.email = 'technomancy@gmail.com'
|
12
|
+
p.summary = 'Bus Scheme is a Scheme in Ruby, imlemented on the bus.'
|
13
|
+
p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
|
14
|
+
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
15
|
+
p.remote_rdoc_dir = ''
|
16
|
+
end
|
17
|
+
|
18
|
+
task :stats do
|
19
|
+
require 'code_statistics'
|
20
|
+
CodeStatistics.new(['lib'], ['Unit tests', 'test']).to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
task :flog do
|
24
|
+
system "flog lib/*rb"
|
25
|
+
end
|
data/bin/bus
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH << File.dirname(__FILE__) + '/../lib'
|
3
|
+
|
4
|
+
require 'bus_scheme'
|
5
|
+
|
6
|
+
if ARGV.empty?
|
7
|
+
BusScheme.repl
|
8
|
+
elsif ARGV.first == '-e' and ARGV.length == 2
|
9
|
+
puts BusScheme.eval_string(ARGV[1])
|
10
|
+
elsif ARGV.length == 1 and File.exist?(ARGV.first) # must be a file
|
11
|
+
puts BusScheme.load(ARGV.first)
|
12
|
+
else
|
13
|
+
puts "Bus Scheme: a scheme interpreter written on the bus.
|
14
|
+
Usage: bus [file | -e \"form\"]
|
15
|
+
Invoking with no arguments enters a REPL
|
16
|
+
With one argument it loads that as a file
|
17
|
+
-e : evals a single form"
|
18
|
+
exit 1
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Array
|
2
|
+
# Lisp-style list access
|
3
|
+
def rest
|
4
|
+
self[1 .. -1]
|
5
|
+
end
|
6
|
+
|
7
|
+
alias_method :car, :first
|
8
|
+
alias_method :cdr, :rest
|
9
|
+
|
10
|
+
# Treat the array as a lambda and call it with given args
|
11
|
+
def call(*args)
|
12
|
+
BusScheme::eval_lambda(self, args)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Simple predicate for convenience
|
16
|
+
def lambda?
|
17
|
+
first == :lambda
|
18
|
+
end
|
19
|
+
end
|
data/lib/bus_scheme.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'readline'
|
5
|
+
require 'yaml'
|
6
|
+
rescue LoadError
|
7
|
+
end
|
8
|
+
|
9
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
10
|
+
require 'object_extensions'
|
11
|
+
require 'array_extensions'
|
12
|
+
require 'parser'
|
13
|
+
require 'eval'
|
14
|
+
|
15
|
+
module BusScheme
|
16
|
+
class ParseError < StandardError; end
|
17
|
+
class EvalError < StandardError; end
|
18
|
+
class ArgumentError < StandardError; end
|
19
|
+
|
20
|
+
VERSION = "0.6"
|
21
|
+
|
22
|
+
PRIMITIVES = {
|
23
|
+
'#t'.intern => true, # :'#t' screws up emacs' ruby parser
|
24
|
+
'#f'.intern => false,
|
25
|
+
|
26
|
+
:+ => lambda { |*args| args.inject(0) { |sum, i| sum + i } },
|
27
|
+
:- => lambda { |x, y| x - y },
|
28
|
+
'/'.intern => lambda { |x, y| x / y },
|
29
|
+
:* => lambda { |*args| args.inject(1) { |product, i| product * i } },
|
30
|
+
|
31
|
+
:> => lambda { |x, y| x > y },
|
32
|
+
:< => lambda { |x, y| x < y },
|
33
|
+
|
34
|
+
:intern => lambda { |x| x.intern },
|
35
|
+
:concat => lambda { |x, y| x + y },
|
36
|
+
:substring => lambda { |x, from, to| x[from .. to] },
|
37
|
+
|
38
|
+
:exit => lambda { exit }, :quit => lambda { exit },
|
39
|
+
}
|
40
|
+
|
41
|
+
SPECIAL_FORMS = {
|
42
|
+
:quote => lambda { |arg| arg },
|
43
|
+
:if => lambda { |condition, yes, *no| eval_form(condition) ? eval_form(yes) : eval_form([:begin] + no) },
|
44
|
+
:begin => lambda { |*args| args.map{ |arg| eval_form(arg) }.last },
|
45
|
+
:set! => lambda { },
|
46
|
+
:lambda => lambda { |args, *form| [:lambda, args] + form },
|
47
|
+
:define => lambda { |sym, definition| BusScheme[sym] = eval_form(definition); sym },
|
48
|
+
}
|
49
|
+
|
50
|
+
SYMBOL_TABLE = {}.merge(PRIMITIVES).merge(SPECIAL_FORMS)
|
51
|
+
SCOPES = [SYMBOL_TABLE]
|
52
|
+
PROMPT = '> '
|
53
|
+
|
54
|
+
# symbol existence predicate
|
55
|
+
def self.in_scope?(symbol)
|
56
|
+
SCOPES.last.has_key?(symbol) or SCOPES.first.has_key?(symbol)
|
57
|
+
end
|
58
|
+
|
59
|
+
# symbol lookup
|
60
|
+
def self.[](symbol)
|
61
|
+
SCOPES.last[symbol] or SCOPES.first[symbol]
|
62
|
+
end
|
63
|
+
|
64
|
+
# symbol assignment to value
|
65
|
+
def self.[]=(symbol, value)
|
66
|
+
SCOPES.last[symbol] = value
|
67
|
+
end
|
68
|
+
|
69
|
+
# remove symbols from all scopes
|
70
|
+
def self.clear_symbols(*symbols)
|
71
|
+
SCOPES.map{ |scope| symbols.map{ |sym| scope.delete sym } }
|
72
|
+
end
|
73
|
+
|
74
|
+
# symbol special form predicate
|
75
|
+
def self.special_form?(symbol)
|
76
|
+
SPECIAL_FORMS.has_key?(symbol)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Read-Eval-Print-Loop
|
80
|
+
def self.repl
|
81
|
+
loop do
|
82
|
+
begin
|
83
|
+
puts BusScheme.eval_string(Readline.readline(PROMPT))
|
84
|
+
rescue Interrupt
|
85
|
+
puts 'Type "(quit)" to leave Bus Scheme.'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/eval.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module BusScheme
|
2
|
+
class << self
|
3
|
+
# Parse a string, then eval the result
|
4
|
+
def eval_string(string)
|
5
|
+
eval_form(parse(string))
|
6
|
+
end
|
7
|
+
|
8
|
+
# Eval a form passed in as an array
|
9
|
+
def eval_form(form)
|
10
|
+
if form == []
|
11
|
+
nil
|
12
|
+
elsif form.is_a? Array
|
13
|
+
apply(form.first, *form.rest)
|
14
|
+
elsif form.is_a? Symbol
|
15
|
+
raise "Undefined symbol: #{form}" unless in_scope?(form)
|
16
|
+
BusScheme[form]
|
17
|
+
else
|
18
|
+
form
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Call a function with given args
|
23
|
+
def apply(function, *args)
|
24
|
+
args.map!{ |arg| eval_form(arg) } unless special_form?(function)
|
25
|
+
|
26
|
+
# refactor me
|
27
|
+
if function.is_a?(Array) and function.lambda?
|
28
|
+
function.call(*args)
|
29
|
+
else
|
30
|
+
raise "Undefined symbol: #{function}" unless in_scope?(function)
|
31
|
+
BusScheme[function].call(*args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# All the super lambda magic happens (or fails to happen) here
|
36
|
+
def eval_lambda(lambda, args)
|
37
|
+
raise BusScheme::EvalError unless lambda.shift == :lambda
|
38
|
+
|
39
|
+
arg_list = lambda.shift
|
40
|
+
raise BusScheme::ArgumentError if !arg_list.is_a?(Array) or arg_list.length != args.length
|
41
|
+
|
42
|
+
SCOPES << {} # new scope
|
43
|
+
until arg_list.empty?
|
44
|
+
BusScheme[arg_list.shift] = args.shift
|
45
|
+
end
|
46
|
+
|
47
|
+
# using affect as a non-return-value-affecting callback
|
48
|
+
BusScheme[:begin].call(*lambda).affect { SCOPES.pop }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/parser.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module BusScheme
|
2
|
+
class << self
|
3
|
+
# Turn an input string into an S-expression
|
4
|
+
def parse(input)
|
5
|
+
parse_tokens tokenize(normalize_whitespace(input))
|
6
|
+
end
|
7
|
+
|
8
|
+
# Turn a list of tokens into a properly-nested S-expression
|
9
|
+
def parse_tokens(tokens)
|
10
|
+
token = tokens.shift
|
11
|
+
if token == :'('
|
12
|
+
parse_list(tokens)
|
13
|
+
else
|
14
|
+
raise BusScheme::ParseError unless tokens.empty?
|
15
|
+
token # atom
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Nest a list from a 1-dimensional list of tokens
|
20
|
+
def parse_list(tokens)
|
21
|
+
[].affect do |list|
|
22
|
+
while element = tokens.shift and element != :')'
|
23
|
+
if element == :'('
|
24
|
+
list << parse_list(tokens)
|
25
|
+
else
|
26
|
+
list << element
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Split an input string into lexically valid tokens
|
33
|
+
def tokenize(input)
|
34
|
+
[].affect do |tokens|
|
35
|
+
while token = pop_token(input)
|
36
|
+
tokens << token
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Take a token off the input string and return it
|
42
|
+
def pop_token(input)
|
43
|
+
token = case input
|
44
|
+
when /^ +/ # whitespace
|
45
|
+
input[0 ... 1] = ''
|
46
|
+
return pop_token(input)
|
47
|
+
when /^\(/ # open paren
|
48
|
+
:'('
|
49
|
+
when /^\)/ # closing paren
|
50
|
+
:')'
|
51
|
+
when /^(\d+)/ # positive integer
|
52
|
+
Regexp.last_match[1].to_i
|
53
|
+
when /^"(.*?)"/ # string
|
54
|
+
Regexp.last_match[1]
|
55
|
+
when /^([^ \)]+)/ # symbol
|
56
|
+
Regexp.last_match[1].intern
|
57
|
+
end
|
58
|
+
# compensate for quotation marks
|
59
|
+
length = token.is_a?(String) ? token.length + 2 : token.to_s.length
|
60
|
+
input[0 .. length - 1] = ''
|
61
|
+
return token
|
62
|
+
end
|
63
|
+
|
64
|
+
# Treat all whitespace in a string as spaces
|
65
|
+
def normalize_whitespace(string)
|
66
|
+
string && string.gsub(/\t/, ' ').gsub(/\n/, ' ').gsub(/ +/, ' ')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/test/foo.scm
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
(define foo 3)
|
data/test/test_eval.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class BusSchemeEvalTest < Test::Unit::TestCase
|
5
|
+
def test_eval_empty_list
|
6
|
+
assert_evals_to nil, []
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_eval_number
|
10
|
+
assert_evals_to 2, 2
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_eval_symbol
|
14
|
+
eval "(define hi 13)"
|
15
|
+
assert_evals_to 13, :hi
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_eval_string
|
19
|
+
assert_evals_to "hi", "\"hi\""
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_eval_function_call
|
23
|
+
assert_evals_to 2, [:+, 1, 1]
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_many_args_for_arithmetic
|
27
|
+
assert_evals_to 4, [:+, 1, 1, 1, 1]
|
28
|
+
assert_evals_to 2, [:*, 1, 2, 1, 1]
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_arithmetic
|
32
|
+
assert_evals_to 2, [:'-', 4, 2]
|
33
|
+
assert_evals_to 2, [:'/', 4, 2]
|
34
|
+
assert_evals_to 2, [:'*', 1, 2]
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_define
|
38
|
+
BusScheme.clear_symbols :foo
|
39
|
+
eval("(define foo 5)")
|
40
|
+
assert_equal 5, BusScheme[:foo]
|
41
|
+
eval("(define foo (quote (5 5 5))")
|
42
|
+
assert_evals_to [5, 5, 5], :foo
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_define_returns_defined_term
|
46
|
+
assert_evals_to :foo, "(define foo 2)"
|
47
|
+
# can't use the eval convenience testing method since it assumes strings are unparsed
|
48
|
+
assert_equal 2, eval("foo")
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_string_primitives
|
52
|
+
assert_evals_to :hi, [:intern, 'hi']
|
53
|
+
assert_evals_to 'helloworld', [:concat, 'hello', 'world']
|
54
|
+
assert_evals_to 'lo', [:substring, 'hello', 3, -1]
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_booleans
|
58
|
+
assert_evals_to false, '#f'
|
59
|
+
assert_evals_to true, '#t'
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_eval_quote
|
63
|
+
assert_evals_to [:'+', 2, 2], [:quote, [:'+', 2, 2]]
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_quote
|
67
|
+
assert_evals_to :hi, [:quote, :hi]
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_nested_arithmetic
|
71
|
+
assert_evals_to 6, [:+, 1, [:+, 1, [:*, 2, 2]]]
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_blows_up_with_undefined_symbol
|
75
|
+
assert_raises(RuntimeError) { eval("undefined-symbol") }
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_variable_substitution
|
79
|
+
eval "(define foo 7)"
|
80
|
+
assert_evals_to 7, :foo
|
81
|
+
assert_evals_to 21, [:*, 3, :foo]
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_if
|
85
|
+
assert_evals_to 7, [:if, '#f'.intern, 3, 7]
|
86
|
+
assert_evals_to 3, [:if, [:>, 8, 2], 3, 7]
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_begin
|
90
|
+
eval([:begin,
|
91
|
+
[:define, :foo, 779],
|
92
|
+
9])
|
93
|
+
assert_equal 779, BusScheme[:foo]
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_set!
|
97
|
+
# i dunno... what does set! do? how's it different from define?
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_simple_lambda
|
101
|
+
assert_equal [:lambda, [], [:+, 1, 1]], eval("(lambda () (+ 1 1))")
|
102
|
+
eval("(define foo (lambda () (+ 1 1)))")
|
103
|
+
assert_equal :lambda, BusScheme[:foo].first
|
104
|
+
assert_evals_to 2, [:foo]
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_lambda_with_arg
|
108
|
+
eval("(define foo (lambda (x) (+ x 1)))")
|
109
|
+
assert_evals_to 2, [:foo, 1]
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_eval_literal_lambda
|
113
|
+
assert_evals_to 4, "((lambda (x) (* x x)) 2)"
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_lambda_with_incorrect_arity
|
117
|
+
eval("(define foo (lambda (x) (+ x 1)))")
|
118
|
+
assert_raises(BusScheme::ArgumentError) { assert_evals_to 2, [:foo, 1, 3] }
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_lambda_args_dont_stay_in_scope
|
122
|
+
BusScheme.clear_symbols(:x, :foo)
|
123
|
+
eval("(define foo (lambda (x) (+ x 1)))")
|
124
|
+
assert !BusScheme.in_scope?(:x)
|
125
|
+
assert_evals_to 2, [:foo, 1]
|
126
|
+
assert !BusScheme.in_scope?(:x)
|
127
|
+
end
|
128
|
+
|
129
|
+
# def test_lexical_scoping
|
130
|
+
# assert_raises(BusScheme::EvalError) do
|
131
|
+
# eval "???"
|
132
|
+
# end
|
133
|
+
# end
|
134
|
+
|
135
|
+
# def test_lambda_closures
|
136
|
+
# eval "(define foo (lambda (x) ((lambda (y) (+ x y)) (* x 2))))"
|
137
|
+
# assert_evals_to 3, [:foo, 1]
|
138
|
+
# end
|
139
|
+
|
140
|
+
# def test_load_file
|
141
|
+
# eval "(load \"#{File.dirname(__FILE__)}/foo.scm\")"
|
142
|
+
# assert_evals_to 3, :foo
|
143
|
+
# end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def eval(form) # convenience method that accepts string or form
|
148
|
+
if form.is_a?(String)
|
149
|
+
BusScheme.eval_string(form)
|
150
|
+
else
|
151
|
+
BusScheme.eval_form(form)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def assert_evals_to(expected, form)
|
156
|
+
assert_equal expected, eval(form)
|
157
|
+
end
|
158
|
+
end
|
data/test/test_helper.rb
ADDED
data/test/test_parser.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class BusSchemeParserTest < Test::Unit::TestCase
|
5
|
+
def test_pop_token
|
6
|
+
string = "(+ 2 2)"
|
7
|
+
assert_equal :'(', BusScheme.pop_token(string)
|
8
|
+
|
9
|
+
assert_equal :'+', BusScheme.pop_token(string)
|
10
|
+
assert_equal 2, BusScheme.pop_token(string)
|
11
|
+
assert_equal 2, BusScheme.pop_token(string)
|
12
|
+
assert_equal :")", BusScheme.pop_token(string)
|
13
|
+
|
14
|
+
string = "\"two\")"
|
15
|
+
assert_equal "two", BusScheme.pop_token(string)
|
16
|
+
assert_equal ")", string
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_tokenize
|
20
|
+
assert_equal [:'(', :'+', 2, 2, :')'], BusScheme.tokenize("(+ 2 2)")
|
21
|
+
assert_equal [:'(', :'+', 2, :'(', :'+', 22, 2, :')', :')'], BusScheme.tokenize("(+ 2 (+ 22 2))")
|
22
|
+
assert_equal [:'(', :plus, 2, 2, :')'], BusScheme.tokenize('(plus 2 2)')
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_parse_numbers
|
26
|
+
assert_parses_to "99", 99
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_parses_strings
|
30
|
+
assert_parses_to "\"hello world\"", "hello world"
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_parses_two_strings
|
34
|
+
assert_parses_to "(concat \"hello\" \"world\")", [:concat, "hello", "world"]
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_parse_list_of_numbers
|
38
|
+
assert_parses_to "(2 2)", [2, 2]
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_parse_list_of_atoms
|
42
|
+
assert_parses_to "(+ 2 2)", [:+, 2, 2]
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_parse_list_of_atoms_with_string
|
46
|
+
assert_parses_to "(+ 2 \"two\")", [:+, 2, "two"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_parse_list_of_nested_sexprs
|
50
|
+
assert_parses_to "(+ 2 (+ 2))", [:+, 2, [:+, 2]]
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_parse_list_of_deeply_nested_sexprs
|
54
|
+
assert_parses_to "(+ 2 (+ 2 (+ 2 2)))", [:+, 2, [:+, 2, [:+, 2, 2]]]
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_parse_two_consecutive_parens_simple
|
58
|
+
assert_parses_to "(let ((foo 2)))", [:let, [[:foo, 2]]]
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_parse_two_consecutive_parens
|
62
|
+
assert_parses_to "(let ((foo 2)) (+ foo 2))", [:let, [[:foo, 2]], [:+, :foo, 2]]
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_whitespace_indifferent
|
66
|
+
assert_parses_equal "(+ 2 2)", "(+ 2 \n \t 2)"
|
67
|
+
end
|
68
|
+
|
69
|
+
# def test_parses_dotted_cons
|
70
|
+
# assert_parses_to "(22 . 11)", [:cons, 22, 11]
|
71
|
+
# assert_parses_to "((+ 2 2) . 11)", [:cons, [:+, 2, 2], 11]
|
72
|
+
# end
|
73
|
+
|
74
|
+
# def test_floats
|
75
|
+
# assert_parses_to "44.9", 44.9
|
76
|
+
# assert_parses_to "0.22", 0.22
|
77
|
+
# assert_parses_to ".22", 0.22
|
78
|
+
# assert_parses_to "2.220", 2.22
|
79
|
+
# end
|
80
|
+
|
81
|
+
# def test_negative_numbers
|
82
|
+
# assert_parses_to "-1", -1
|
83
|
+
# assert_parses_to "-0", 0
|
84
|
+
# assert_parses_to "-02", -2
|
85
|
+
# end
|
86
|
+
|
87
|
+
# def test_negative_floats
|
88
|
+
# assert_parses_to "-0.22", -0.22
|
89
|
+
# assert_parses_to "-.22", -0.22
|
90
|
+
# assert_parses_to "-0.10", -0.1
|
91
|
+
# end
|
92
|
+
|
93
|
+
# def test_character_literals
|
94
|
+
# assert_parses_to "?e", "e"
|
95
|
+
# assert_parses_to "?A", "A"
|
96
|
+
# # what else?
|
97
|
+
# end
|
98
|
+
|
99
|
+
def test_parse_random_elisp_form_from_my_dot_emacs
|
100
|
+
lisp = "(let ((system-specific-config
|
101
|
+
(concat \"~/.emacs.d/\"
|
102
|
+
(shell-command-to-string \"hostname\"))))
|
103
|
+
(if (file-exists-p system-specific-config)
|
104
|
+
(load system-specific-config)))"
|
105
|
+
assert_parses_to(lisp,
|
106
|
+
[:let, [[:'system-specific-config',
|
107
|
+
[:concat, "~/.emacs.d/",
|
108
|
+
[:'shell-command-to-string', "hostname"]]]],
|
109
|
+
[:if, [:'file-exists-p', :'system-specific-config'],
|
110
|
+
[:load, :'system-specific-config']]])
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def assert_parses_to(actual_string, expected)
|
116
|
+
assert_equal expected, BusScheme.parse(actual_string)
|
117
|
+
end
|
118
|
+
|
119
|
+
def assert_parses_equal(one, two)
|
120
|
+
assert_equal BusScheme.parse(one), BusScheme.parse(two)
|
121
|
+
end
|
122
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bus-scheme
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.6"
|
5
|
+
platform: ""
|
6
|
+
authors:
|
7
|
+
- Phil Hagelberg
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2007-12-17 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.3.0
|
23
|
+
version:
|
24
|
+
description: Bus Scheme is a Scheme written in Ruby, but implemented on the bus! Every programmer must implement Scheme as a rite of passage; this is mine. Note that all the implementation of Bus Scheme must be written while on a bus. Documentation, tests, and administrivia may be accomplished elsewhere, but all actual implementation code is strictly bus-driven. Patches are welcome as long as they were written while riding a bus. (If your daily commute does not involve a bus but you want to submit a patch, we may be able to work something out regarding code written on trains, ferries, or perhaps even carpool lanes.) Bus Scheme is primarily a toy; using it for anything serious is (right now) ill-advised. Bus Scheme aims for general Scheme usefulness optimized for learning and fun. It's not targeting R5RS or anything like that. == Install * sudo gem install bus-scheme
|
25
|
+
email: technomancy@gmail.com
|
26
|
+
executables:
|
27
|
+
- bus
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- Manifest.txt
|
32
|
+
- README.txt
|
33
|
+
files:
|
34
|
+
- COPYING
|
35
|
+
- Manifest.txt
|
36
|
+
- README.txt
|
37
|
+
- Rakefile
|
38
|
+
- bin/bus
|
39
|
+
- lib/array_extensions.rb
|
40
|
+
- lib/bus_scheme.rb
|
41
|
+
- lib/eval.rb
|
42
|
+
- lib/object_extensions.rb
|
43
|
+
- lib/parser.rb
|
44
|
+
- test/foo.scm
|
45
|
+
- test/test_eval.rb
|
46
|
+
- test/test_helper.rb
|
47
|
+
- test/test_parser.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: " by Phil Hagelberg (c) 2007"
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --main
|
53
|
+
- README.txt
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project: bus-scheme
|
71
|
+
rubygems_version: 0.9.5
|
72
|
+
signing_key:
|
73
|
+
specification_version: 2
|
74
|
+
summary: Bus Scheme is a Scheme in Ruby, imlemented on the bus.
|
75
|
+
test_files:
|
76
|
+
- test/test_parser.rb
|
77
|
+
- test/test_helper.rb
|
78
|
+
- test/test_eval.rb
|