bus-scheme 0.6
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/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
|