bus-scheme 0.7.1 → 0.7.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +5 -1
- data/README.txt +18 -9
- data/Rakefile +14 -0
- data/lib/array_extensions.rb +10 -0
- data/lib/bus_scheme.rb +8 -20
- data/lib/cons.rb +41 -0
- data/lib/eval.rb +3 -4
- data/lib/lambda.rb +18 -13
- data/lib/object_extensions.rb +4 -0
- data/lib/parser.rb +27 -19
- data/lib/primitives.rb +45 -0
- data/lib/scheme/core.scm +35 -0
- data/test/test_core.rb +43 -0
- data/test/test_eval.rb +8 -75
- data/test/test_helper.rb +0 -7
- data/test/test_lambda.rb +3 -3
- data/test/test_parser.rb +57 -35
- data/test/test_primitives.rb +118 -0
- metadata +10 -5
- data/lib/definitions.rb +0 -43
- data/test/test_compiler.rb +0 -19
data/Manifest.txt
CHANGED
@@ -5,13 +5,17 @@ Rakefile
|
|
5
5
|
bin/bus
|
6
6
|
lib/array_extensions.rb
|
7
7
|
lib/bus_scheme.rb
|
8
|
+
lib/cons.rb
|
8
9
|
lib/eval.rb
|
9
10
|
lib/lambda.rb
|
10
|
-
lib/definitions.rb
|
11
11
|
lib/object_extensions.rb
|
12
12
|
lib/parser.rb
|
13
|
+
lib/primitives.rb
|
14
|
+
lib/scheme/core.scm
|
13
15
|
test/foo.scm
|
16
|
+
test/test_core.rb
|
14
17
|
test/test_eval.rb
|
15
18
|
test/test_helper.rb
|
16
19
|
test/test_lambda.rb
|
17
20
|
test/test_parser.rb
|
21
|
+
test/test_primitives.rb
|
data/README.txt
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
Bus Scheme
|
2
|
-
by Phil Hagelberg (c) 2007
|
2
|
+
by Phil Hagelberg (c) 2007 - 2008
|
3
3
|
http://bus-scheme.rubyforge.org
|
4
4
|
|
5
5
|
== Description
|
@@ -39,18 +39,27 @@ $ bus foo.scm # load a file
|
|
39
39
|
|
40
40
|
Bus Scheme is currently missing pieces of functionality:
|
41
41
|
|
42
|
-
|
43
|
-
*
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
*
|
42
|
+
=== Parser
|
43
|
+
* parse character literals
|
44
|
+
** alternate define lambda forms
|
45
|
+
** parse dotted cons cells
|
46
|
+
*** look up and enforce rules for identifier names
|
47
|
+
|
48
|
+
=== General
|
49
|
+
* lambda args should get passed in as lists by default, not vectors/arrays
|
50
|
+
* (lambda args body) for rest args
|
51
|
+
* stack traces on error plz
|
52
|
+
** optimize tail call recursion
|
53
|
+
** some kind of load path?
|
50
54
|
|
51
55
|
Failing tests for some of these are already included (commented out,
|
52
56
|
mostly) in the relevant test files.
|
53
57
|
|
58
|
+
=== Long Term (post 1.0)
|
59
|
+
* continuations (?!?)
|
60
|
+
* compile to Rubinius bytecode
|
61
|
+
* parse rationals, scientific, complex, and polar complex numbers
|
62
|
+
|
54
63
|
== Requirements
|
55
64
|
|
56
65
|
Bus Scheme should run on (at least) Ruby 1.8, Ruby 1.9, and Rubinius.
|
data/Rakefile
CHANGED
@@ -15,11 +15,25 @@ Hoe.new('bus-scheme', BusScheme::VERSION) do |p|
|
|
15
15
|
p.remote_rdoc_dir = ''
|
16
16
|
end
|
17
17
|
|
18
|
+
desc "Code statistics"
|
18
19
|
task :stats do
|
19
20
|
require 'code_statistics'
|
20
21
|
CodeStatistics.new(['lib'], ['Unit tests', 'test']).to_s
|
21
22
|
end
|
22
23
|
|
24
|
+
desc "Complexity statistics"
|
23
25
|
task :flog do
|
24
26
|
system "flog lib/*rb"
|
25
27
|
end
|
28
|
+
|
29
|
+
desc "Show todo items"
|
30
|
+
task :todo do
|
31
|
+
puts File.read('README.txt').match(/== Todo(.*)== Requirements/m)[1].split("\n").grep(/^( \*|===)/).join("\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Show tests that have been commented out"
|
35
|
+
task :commented_tests do
|
36
|
+
Dir.glob('test/test_*.rb').each do |file|
|
37
|
+
puts File.read(file).grep(/^\s*#\s*def (test_[^ ]*)/)
|
38
|
+
end
|
39
|
+
end
|
data/lib/array_extensions.rb
CHANGED
@@ -6,6 +6,16 @@ class Array
|
|
6
6
|
|
7
7
|
alias_method :car, :first
|
8
8
|
alias_method :cdr, :rest
|
9
|
+
|
10
|
+
def to_list
|
11
|
+
if self.cdr.empty?
|
12
|
+
BusScheme::Cons.new(self.car, nil)
|
13
|
+
else
|
14
|
+
BusScheme::Cons.new(self.car, self.cdr.to_list)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :to_sexp, :to_list
|
9
19
|
end
|
10
20
|
|
11
21
|
module Enumerable # for 1.9, zip is defined on Enumerable
|
data/lib/bus_scheme.rb
CHANGED
@@ -11,32 +11,16 @@ require 'object_extensions'
|
|
11
11
|
require 'array_extensions'
|
12
12
|
require 'parser'
|
13
13
|
require 'eval'
|
14
|
-
require '
|
14
|
+
require 'primitives'
|
15
|
+
require 'cons'
|
15
16
|
require 'lambda'
|
16
17
|
|
17
18
|
module BusScheme
|
18
|
-
VERSION = "0.7.
|
19
|
+
VERSION = "0.7.5"
|
19
20
|
|
20
21
|
SYMBOL_TABLE = {}.merge(PRIMITIVES).merge(SPECIAL_FORMS)
|
21
22
|
PROMPT = '> '
|
22
23
|
|
23
|
-
# what scope is appropraite for this symbol
|
24
|
-
def self.scope_of(symbol)
|
25
|
-
[Lambda.scope, SYMBOL_TABLE].compact.detect { |scope| scope.has_key?(symbol) }
|
26
|
-
end
|
27
|
-
|
28
|
-
# symbol lookup
|
29
|
-
def self.[](symbol)
|
30
|
-
scope = scope_of(symbol)
|
31
|
-
raise EvalError.new("Undefined symbol: #{symbol}") unless scope
|
32
|
-
scope && scope[symbol]
|
33
|
-
end
|
34
|
-
|
35
|
-
# symbol assignment to value
|
36
|
-
def self.[]=(symbol, value)
|
37
|
-
(scope_of(symbol) || Lambda.scope || SYMBOL_TABLE)[symbol] = value
|
38
|
-
end
|
39
|
-
|
40
24
|
# symbol special form predicate
|
41
25
|
def self.special_form?(symbol)
|
42
26
|
SPECIAL_FORMS.has_key?(symbol)
|
@@ -46,7 +30,9 @@ module BusScheme
|
|
46
30
|
def self.repl
|
47
31
|
loop do
|
48
32
|
puts begin
|
49
|
-
|
33
|
+
input = Readline.readline(PROMPT)
|
34
|
+
exit if input.nil? # only Ctrl-D produces nil here it seems
|
35
|
+
BusScheme.eval_string input
|
50
36
|
rescue Interrupt
|
51
37
|
'Type "(quit)" to leave Bus Scheme.'
|
52
38
|
rescue BusSchemeError => e
|
@@ -57,4 +43,6 @@ module BusScheme
|
|
57
43
|
end
|
58
44
|
end
|
59
45
|
end
|
46
|
+
|
47
|
+
['core'].each { |file| SYMBOL_TABLE[:load].call("#{File.dirname(__FILE__)}/scheme/#{file}.scm") }
|
60
48
|
end
|
data/lib/cons.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module BusScheme
|
2
|
+
class Cons
|
3
|
+
attr_accessor :car, :cdr
|
4
|
+
|
5
|
+
def initialize(car, cdr = nil)
|
6
|
+
@car, @cdr = [car, cdr]
|
7
|
+
end
|
8
|
+
|
9
|
+
def ==(other)
|
10
|
+
@car == other.car and @cdr == other.cdr
|
11
|
+
end
|
12
|
+
|
13
|
+
alias_method :first, :car
|
14
|
+
alias_method :rest, :cdr
|
15
|
+
|
16
|
+
def to_a
|
17
|
+
if @cdr.respond_to? :to_a
|
18
|
+
[@car] + @cdr.to_a
|
19
|
+
elsif !@cdr.nil?
|
20
|
+
[@car, @cdr]
|
21
|
+
else
|
22
|
+
[@car]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect(open = '(', close = ')')
|
27
|
+
str = open + @car.inspect
|
28
|
+
if @cdr.nil?
|
29
|
+
str + close
|
30
|
+
elsif @cdr.is_a? Cons
|
31
|
+
str + ' ' + @cdr.inspect('', '') + close
|
32
|
+
else
|
33
|
+
str + ' . ' + @cdr.inspect + close
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def cons(car, cdr = nil)
|
39
|
+
Cons.new(car, cdr)
|
40
|
+
end
|
41
|
+
end
|
data/lib/eval.rb
CHANGED
@@ -7,12 +7,11 @@ module BusScheme
|
|
7
7
|
|
8
8
|
# Eval a form passed in as an array
|
9
9
|
def eval_form(form)
|
10
|
-
if form
|
11
|
-
nil
|
12
|
-
elsif form.is_a? Array
|
10
|
+
if form.is_a? Array or form.is_a? Cons and form.first
|
13
11
|
apply(form.first, *form.rest)
|
14
12
|
elsif form.is_a? Symbol
|
15
|
-
|
13
|
+
raise EvalError.new("Undefined symbol: #{form}") unless Lambda.scope.has_key?(form)
|
14
|
+
Lambda.scope[form]
|
16
15
|
else # well it must be a literal then
|
17
16
|
form
|
18
17
|
end
|
data/lib/lambda.rb
CHANGED
@@ -1,30 +1,34 @@
|
|
1
1
|
module BusScheme
|
2
|
-
|
3
|
-
|
2
|
+
# The RecursiveHash is needed to store Lambda environments
|
3
|
+
class RecursiveHash < Hash
|
4
|
+
# takes a hash and a parent
|
5
|
+
def initialize(hash, parent)
|
4
6
|
@parent = parent
|
5
|
-
|
7
|
+
hash.each { |k, v| self[k] = v }
|
6
8
|
end
|
7
9
|
|
8
|
-
alias_method :
|
10
|
+
alias_method :immediate_has_key?, :has_key?
|
11
|
+
alias_method :immediate_set, :[]=
|
12
|
+
alias_method :immediate_lookup, :[]
|
13
|
+
|
9
14
|
def has_key?(symbol)
|
10
|
-
|
15
|
+
immediate_has_key?(symbol) or @parent && @parent.has_key?(symbol)
|
11
16
|
end
|
12
17
|
|
13
|
-
alias_method :lookup, :[]
|
14
18
|
def [](symbol)
|
15
|
-
|
19
|
+
immediate_lookup(symbol) or @parent && @parent[symbol]
|
16
20
|
end
|
17
21
|
|
18
|
-
alias_method :old_set, :[]=
|
19
22
|
def []=(symbol, value)
|
20
|
-
if !
|
23
|
+
if !immediate_has_key?(symbol) and @parent and @parent.has_key?(symbol)
|
21
24
|
@parent[symbol] = value
|
22
25
|
else
|
23
|
-
|
26
|
+
immediate_set symbol, value
|
24
27
|
end
|
25
28
|
end
|
26
29
|
end
|
27
30
|
|
31
|
+
# Lambdas are closures.
|
28
32
|
class Lambda
|
29
33
|
@@stack = []
|
30
34
|
|
@@ -38,13 +42,14 @@ module BusScheme
|
|
38
42
|
# execute lambda with given arg_values
|
39
43
|
def call(*arg_values)
|
40
44
|
raise BusScheme::ArgumentError if @arg_names.length != arg_values.length
|
41
|
-
@scope =
|
45
|
+
@scope = RecursiveHash.new(@arg_names.zip(arg_values).to_hash, @enclosing_scope)
|
42
46
|
@@stack << self
|
43
|
-
BusScheme
|
47
|
+
BusScheme.eval_form(@body.unshift(:begin)).affect { @@stack.pop }
|
44
48
|
end
|
45
49
|
|
50
|
+
# What's the current scope?
|
46
51
|
def self.scope
|
47
|
-
@@stack.empty? ?
|
52
|
+
@@stack.empty? ? SYMBOL_TABLE : @@stack.last.scope
|
48
53
|
end
|
49
54
|
end
|
50
55
|
end
|
data/lib/object_extensions.rb
CHANGED
data/lib/parser.rb
CHANGED
@@ -2,7 +2,7 @@ module BusScheme
|
|
2
2
|
class << self
|
3
3
|
# Turn an input string into an S-expression
|
4
4
|
def parse(input)
|
5
|
-
parse_tokens tokenize(
|
5
|
+
parse_tokens tokenize(input).flatten
|
6
6
|
end
|
7
7
|
|
8
8
|
# Turn a list of tokens into a properly-nested S-expression
|
@@ -40,30 +40,38 @@ module BusScheme
|
|
40
40
|
|
41
41
|
# Take a token off the input string and return it
|
42
42
|
def pop_token(input)
|
43
|
+
# can't use ^ since it matches line beginnings in mid-string
|
43
44
|
token = case input
|
44
|
-
when
|
45
|
-
input[0
|
45
|
+
when /\A(\s|;.*$)/ # ignore whitespace and comments
|
46
|
+
input[0 .. Regexp.last_match[1].length - 1] = ''
|
46
47
|
return pop_token(input)
|
47
|
-
when
|
48
|
-
|
49
|
-
when
|
50
|
-
|
51
|
-
|
48
|
+
when /\A(\(|\))/ # parens
|
49
|
+
Regexp.last_match[1].intern
|
50
|
+
when /\A#\(/ # vector
|
51
|
+
input[0 ... 2] = ''
|
52
|
+
return [:'(', :vector, tokenize(input)]
|
53
|
+
when /\A'/ # single-quote
|
54
|
+
input[0 ... 1] = ''
|
55
|
+
return [:'(', :quote,
|
56
|
+
if input[0 ... 1] == '('
|
57
|
+
tokenize(input)
|
58
|
+
else
|
59
|
+
pop_token(input)
|
60
|
+
end,
|
61
|
+
:')']
|
62
|
+
when /\A(-?[0-9]*\.[0-9]+)/ # float
|
63
|
+
Regexp.last_match[1].to_f
|
64
|
+
when /\A(-?[0-9]+)/ # integer
|
52
65
|
Regexp.last_match[1].to_i
|
53
|
-
when
|
54
|
-
Regexp.last_match[
|
55
|
-
when
|
66
|
+
when /\A("(.*?)")/ # string
|
67
|
+
Regexp.last_match[2]
|
68
|
+
when /\A([^ \n\)]+)/ # symbol
|
56
69
|
Regexp.last_match[1].intern
|
57
70
|
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
71
|
|
64
|
-
|
65
|
-
|
66
|
-
|
72
|
+
# Remove the matched part from the string
|
73
|
+
input[0 .. Regexp.last_match[1].length - 1] = '' if token
|
74
|
+
return token
|
67
75
|
end
|
68
76
|
end
|
69
77
|
end
|
data/lib/primitives.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module BusScheme
|
2
|
+
class BusSchemeError < StandardError; end
|
3
|
+
class ParseError < BusSchemeError; end
|
4
|
+
class EvalError < BusSchemeError; end
|
5
|
+
class ArgumentError < BusSchemeError; end
|
6
|
+
|
7
|
+
PRIMITIVES = {
|
8
|
+
# right now I believe there are as few things implemented primitively as possible
|
9
|
+
# except for functions that require splat args. do we need something like &rest?
|
10
|
+
|
11
|
+
'#t'.intern => true, # :'#t' screws up emacs' ruby parser
|
12
|
+
'#f'.intern => false,
|
13
|
+
|
14
|
+
:+ => lambda { |*args| args.inject { |sum, i| sum + i } },
|
15
|
+
:- => lambda { |x, y| x - y },
|
16
|
+
:* => lambda { |*args| args.inject { |product, i| product * i } },
|
17
|
+
'/'.intern => lambda { |x, y| x / y },
|
18
|
+
|
19
|
+
:concat => lambda { |*args| args.join('') },
|
20
|
+
:cons => lambda { |car, cdr| Cons.new(car, cdr) },
|
21
|
+
# todo: lambda args should come as lists by default, not vectors/arrays
|
22
|
+
:list => lambda { |*members| members.to_list },
|
23
|
+
:vector => lambda { |*members| members },
|
24
|
+
|
25
|
+
:ruby => lambda { |*code| eval(code.join('')) },
|
26
|
+
:eval => lambda { |code| eval_form(code) },
|
27
|
+
:send => lambda { |obj, *message| obj.send(*message) },
|
28
|
+
:load => lambda { |filename| eval_string("(begin #{File.read(filename)} )") },
|
29
|
+
:exit => lambda { exit }, :quit => lambda { exit },
|
30
|
+
}
|
31
|
+
|
32
|
+
# if we add in macros, can some of these be defined in scheme?
|
33
|
+
SPECIAL_FORMS = {
|
34
|
+
:quote => lambda { |arg| arg.to_sexp },
|
35
|
+
:if => lambda { |q, yes, *no| eval_form(q) ? eval_form(yes) : eval_form([:begin] + no) },
|
36
|
+
:begin => lambda { |*args| args.map{ |arg| eval_form(arg) }.last },
|
37
|
+
:set! => lambda { |sym, value| raise EvalError.new unless Lambda.scope.has_key?(sym) and
|
38
|
+
Lambda.scope[sym] = eval_form(value); sym },
|
39
|
+
:lambda => lambda { |args, *form| Lambda.new(args, form) },
|
40
|
+
:define => lambda { |sym, definition| Lambda.scope[sym] = eval_form(definition); sym },
|
41
|
+
|
42
|
+
# once we have macros, this can be defined in scheme
|
43
|
+
:let => lambda { |defs, *body| Lambda.new(defs.map{ |d| d.car }, body).call(*defs.map{ |d| eval_form d.last }) }
|
44
|
+
}
|
45
|
+
end
|
data/lib/scheme/core.scm
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
(define intern
|
2
|
+
(lambda (sym) (send sym (quote intern))))
|
3
|
+
|
4
|
+
(define substring
|
5
|
+
(lambda (string to from) (send string (quote []) to from)))
|
6
|
+
|
7
|
+
(define null?
|
8
|
+
(lambda (expr) (= expr ())))
|
9
|
+
|
10
|
+
(define >
|
11
|
+
(lambda (x y) (send x (intern ">") y)))
|
12
|
+
|
13
|
+
(define <
|
14
|
+
(lambda (x y) (send x (intern "<") y)))
|
15
|
+
|
16
|
+
(define =
|
17
|
+
(lambda (x y) (send x (intern "==") y)))
|
18
|
+
|
19
|
+
(define and
|
20
|
+
(lambda (x y) (if x (if y y #f) #f)))
|
21
|
+
|
22
|
+
(define or
|
23
|
+
(lambda (x y) (if x x (if y y #f))))
|
24
|
+
|
25
|
+
(define not
|
26
|
+
(lambda (expr) (if expr #f #t)))
|
27
|
+
|
28
|
+
(define car
|
29
|
+
(lambda (lst) (send lst (quote first))))
|
30
|
+
|
31
|
+
(define cdr
|
32
|
+
(lambda (lst) (send lst (quote rest))))
|
33
|
+
|
34
|
+
(define cadr
|
35
|
+
(lambda (lst) (car (cdr lst))))
|
data/test/test_core.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class CoreTest < Test::Unit::TestCase
|
5
|
+
def test_comparison
|
6
|
+
assert_evals_to true, "(null? ())"
|
7
|
+
assert_evals_to false, "(null? 43)"
|
8
|
+
assert_evals_to true, "(> 4 2)"
|
9
|
+
assert_evals_to false, "(> 9 13)"
|
10
|
+
assert_evals_to true, "(= 4 4)"
|
11
|
+
assert_evals_to false, "(= (+ 2 2) 5)"
|
12
|
+
assert_evals_to true, "(not #f)"
|
13
|
+
assert_evals_to false, "(not #t)"
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_string_functions
|
17
|
+
assert_evals_to :hi, [:intern, 'hi']
|
18
|
+
assert_evals_to 'lo', [:substring, 'hello', 3, 5]
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_list_functions
|
22
|
+
assert_evals_to :foo, "(car (cons (quote foo) (quote bar)))"
|
23
|
+
assert_evals_to :bar, "(cdr (cons (quote foo) (quote bar)))"
|
24
|
+
assert_equal(BusScheme::Cons.new(:foo, BusScheme::Cons.new(:bar, nil)),
|
25
|
+
[:foo, :bar].to_list)
|
26
|
+
assert_evals_to(BusScheme::Cons.new(2, BusScheme::Cons.new(3, nil)),
|
27
|
+
"(list 2 3)")
|
28
|
+
assert_evals_to "bar", "(cadr (list \"foo\" \"bar\")"
|
29
|
+
assert_evals_to [1, :foo].to_list, "(list 1 'foo)"
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_boolean_logic
|
33
|
+
assert_evals_to true, "(and #t #t)"
|
34
|
+
assert_evals_to false, "(and #t #f)"
|
35
|
+
assert_evals_to false, "(and #f #t)"
|
36
|
+
assert_evals_to false, "(and #f #f)"
|
37
|
+
|
38
|
+
assert_evals_to true, "(or #t #t)"
|
39
|
+
assert_evals_to true, "(or #t #f)"
|
40
|
+
assert_evals_to true, "(or #f #t)"
|
41
|
+
assert_evals_to false, "(or #f #f)"
|
42
|
+
end
|
43
|
+
end
|
data/test/test_eval.rb
CHANGED
@@ -3,7 +3,8 @@ require 'test_helper'
|
|
3
3
|
|
4
4
|
class BusSchemeEvalTest < Test::Unit::TestCase
|
5
5
|
def test_eval_empty_list
|
6
|
-
assert_evals_to
|
6
|
+
assert_evals_to [], []
|
7
|
+
assert_evals_to true, "(if () #t #f)"
|
7
8
|
end
|
8
9
|
|
9
10
|
def test_eval_number
|
@@ -11,12 +12,12 @@ class BusSchemeEvalTest < Test::Unit::TestCase
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def test_set_symbol
|
14
|
-
BusScheme[:hi] = 'hi'
|
15
|
-
assert BusScheme::
|
15
|
+
BusScheme::Lambda.scope[:hi] = 'hi'
|
16
|
+
assert BusScheme::Lambda.scope[:hi]
|
16
17
|
end
|
17
18
|
|
18
19
|
def test_eval_symbol
|
19
|
-
|
20
|
+
BusScheme::Lambda.scope[:hi] = 13
|
20
21
|
assert_evals_to 13, :hi
|
21
22
|
end
|
22
23
|
|
@@ -28,49 +29,6 @@ class BusSchemeEvalTest < Test::Unit::TestCase
|
|
28
29
|
assert_evals_to 2, [:+, 1, 1]
|
29
30
|
end
|
30
31
|
|
31
|
-
def test_many_args_for_arithmetic
|
32
|
-
assert_evals_to 4, [:+, 1, 1, 1, 1]
|
33
|
-
assert_evals_to 2, [:*, 1, 2, 1, 1]
|
34
|
-
end
|
35
|
-
|
36
|
-
def test_arithmetic
|
37
|
-
assert_evals_to 2, [:'-', 4, 2]
|
38
|
-
assert_evals_to 2, [:'/', 4, 2]
|
39
|
-
assert_evals_to 2, [:'*', 1, 2]
|
40
|
-
end
|
41
|
-
|
42
|
-
def test_define
|
43
|
-
clear_symbols :foo
|
44
|
-
eval("(define foo 5)")
|
45
|
-
assert_equal 5, BusScheme[:foo]
|
46
|
-
eval("(define foo (quote (5 5 5))")
|
47
|
-
assert_evals_to [5, 5, 5], :foo
|
48
|
-
end
|
49
|
-
|
50
|
-
def test_define_returns_defined_term
|
51
|
-
assert_evals_to :foo, "(define foo 2)"
|
52
|
-
assert_equal 2, eval("foo")
|
53
|
-
end
|
54
|
-
|
55
|
-
def test_string_primitives
|
56
|
-
assert_evals_to :hi, [:intern, 'hi']
|
57
|
-
assert_evals_to 'lo', [:substring, 'hello', 3, -1]
|
58
|
-
end
|
59
|
-
|
60
|
-
def test_booleans
|
61
|
-
assert BusScheme::PRIMITIVES.has_key? '#f'.intern
|
62
|
-
assert_evals_to false, '#f'
|
63
|
-
assert_evals_to true, '#t'
|
64
|
-
end
|
65
|
-
|
66
|
-
def test_eval_quote
|
67
|
-
assert_evals_to [:'+', 2, 2], [:quote, [:'+', 2, 2]]
|
68
|
-
end
|
69
|
-
|
70
|
-
def test_quote
|
71
|
-
assert_evals_to :hi, [:quote, :hi]
|
72
|
-
end
|
73
|
-
|
74
32
|
def test_nested_arithmetic
|
75
33
|
assert_evals_to 6, [:+, 1, [:+, 1, [:*, 2, 2]]]
|
76
34
|
end
|
@@ -85,33 +43,8 @@ class BusSchemeEvalTest < Test::Unit::TestCase
|
|
85
43
|
assert_evals_to 21, [:*, 3, :foo]
|
86
44
|
end
|
87
45
|
|
88
|
-
def
|
89
|
-
assert_evals_to
|
90
|
-
assert_evals_to
|
91
|
-
end
|
92
|
-
|
93
|
-
def test_begin
|
94
|
-
eval([:begin,
|
95
|
-
[:define, :foo, 779],
|
96
|
-
9])
|
97
|
-
assert_equal 779, BusScheme[:foo]
|
98
|
-
end
|
99
|
-
|
100
|
-
def test_set!
|
101
|
-
clear_symbols(:foo)
|
102
|
-
# can only set! existing variables
|
103
|
-
assert_raises(BusScheme::EvalError) { eval "(set! foo 7)" }
|
104
|
-
eval "(define foo 3)"
|
105
|
-
eval "(set! foo 7)"
|
106
|
-
assert_evals_to 7, :foo
|
107
|
-
end
|
108
|
-
|
109
|
-
def test_load_file
|
110
|
-
eval "(load \"#{File.dirname(__FILE__)}/foo.scm\")"
|
111
|
-
assert_evals_to 3, :foo
|
112
|
-
end
|
113
|
-
|
114
|
-
def test_eval_ruby
|
115
|
-
assert_evals_to "foofoofoo", "(ruby \"'foo' * 3\")"
|
46
|
+
def test_single_quote
|
47
|
+
assert_evals_to :foo, "'foo"
|
48
|
+
assert_evals_to [:foo, :biz, :bbb].to_list, "'(foo biz bbb)"
|
116
49
|
end
|
117
50
|
end
|
data/test/test_helper.rb
CHANGED
data/test/test_lambda.rb
CHANGED
@@ -13,7 +13,7 @@ class BusSchemeLambdaTest < Test::Unit::TestCase
|
|
13
13
|
assert_equal [], l.arg_names
|
14
14
|
|
15
15
|
eval("(define foo (lambda () (+ 1 1)))")
|
16
|
-
assert BusScheme[:foo].is_a?(BusScheme::Lambda)
|
16
|
+
assert BusScheme::Lambda.scope[:foo].is_a?(BusScheme::Lambda)
|
17
17
|
assert_evals_to 2, [:foo]
|
18
18
|
end
|
19
19
|
|
@@ -34,9 +34,9 @@ class BusSchemeLambdaTest < Test::Unit::TestCase
|
|
34
34
|
def test_lambda_args_dont_stay_in_scope
|
35
35
|
clear_symbols(:x, :foo)
|
36
36
|
eval("(define foo (lambda (x) (+ x 1)))")
|
37
|
-
assert_nil BusScheme.
|
37
|
+
assert_nil BusScheme::Lambda.scope[:x]
|
38
38
|
assert_evals_to 2, [:foo, 1]
|
39
|
-
assert_nil BusScheme.
|
39
|
+
assert_nil BusScheme::Lambda.scope[:x]
|
40
40
|
end
|
41
41
|
|
42
42
|
def test_lambda_calls_lambda
|
data/test/test_parser.rb
CHANGED
@@ -63,39 +63,61 @@ class BusSchemeParserTest < Test::Unit::TestCase
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def test_whitespace_indifferent
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
# assert_parses_to "?A", "A"
|
96
|
-
# # what else?
|
97
|
-
# end
|
66
|
+
assert_equal 3, BusScheme.pop_token("3 2\n2")
|
67
|
+
assert_parses_equal "(+ 2 2)", "(+ 2 2)", "confused by spaces"
|
68
|
+
assert_parses_equal "(+ 2 2)", "(+ 2 \t 2)", "confused by tab"
|
69
|
+
assert_parses_equal "(+ 2 2)", "(+ 2\n2)", "confused by newline"
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_parses_vectors
|
73
|
+
assert_equal [:'(', :vector, 1, 2, :')'], BusScheme::tokenize("#(1 2)").flatten
|
74
|
+
assert_parses_to "#(1 2)", [:vector, 1, 2]
|
75
|
+
assert_parses_to "#(1 (2 3 4)", [:vector, 1, [2, 3, 4]]
|
76
|
+
end
|
77
|
+
|
78
|
+
# def test_parses_dotted_cons
|
79
|
+
# assert_parses_to "(22 . 11)", [:cons, 22, 11]
|
80
|
+
# assert_parses_to "((+ 2 2) . 11)", [:cons, [:+, 2, 2], 11]
|
81
|
+
# end
|
82
|
+
|
83
|
+
def test_floats
|
84
|
+
assert_parses_to "44.9", 44.9
|
85
|
+
assert_parses_to "0.22", 0.22
|
86
|
+
assert_parses_to ".22", 0.22
|
87
|
+
assert_parses_to "2.220", 2.22
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_negative_ints
|
91
|
+
assert_parses_to "-1", -1
|
92
|
+
assert_parses_to "-0", 0
|
93
|
+
assert_parses_to "-02", -2
|
94
|
+
end
|
98
95
|
|
96
|
+
def test_negative_floats
|
97
|
+
assert_parses_to "-0.22", -0.22
|
98
|
+
assert_parses_to "-.22", -0.22
|
99
|
+
assert_parses_to "-0.10", -0.1
|
100
|
+
end
|
101
|
+
|
102
|
+
# def test_character_literals
|
103
|
+
# assert_parses_to "?#e", "e"
|
104
|
+
# assert_parses_to "?#A", "A"
|
105
|
+
# end
|
106
|
+
|
107
|
+
def test_quote
|
108
|
+
assert_parses_to "'foo", [:quote, :foo]
|
109
|
+
assert_equal [:'(', :quote, :'(', :foo, :bar, :baz, :')', :')'], BusScheme::tokenize("'(foo bar baz)").flatten
|
110
|
+
assert_parses_to "'(foo bar baz)", [:quote, [:foo, :bar, :baz]]
|
111
|
+
end
|
112
|
+
|
113
|
+
# have to change normalize_whitespace to not turn newlines into spaces for this to work
|
114
|
+
def test_ignore_comments
|
115
|
+
assert_parses_to ";; hello", nil
|
116
|
+
assert_parses_to "12 ;; comment", 12
|
117
|
+
assert_parses_to "(+ 2;; this is a mid-sexp comment
|
118
|
+
2)", [:+, 2, 2]
|
119
|
+
end
|
120
|
+
|
99
121
|
def test_parse_random_elisp_form_from_my_dot_emacs
|
100
122
|
lisp = "(let ((system-specific-config
|
101
123
|
(concat \"~/.emacs.d/\"
|
@@ -108,7 +130,7 @@ class BusSchemeParserTest < Test::Unit::TestCase
|
|
108
130
|
[:'shell-command-to-string', "hostname"]]]],
|
109
131
|
[:if, [:'file-exists-p', :'system-specific-config'],
|
110
132
|
[:load, :'system-specific-config']]])
|
111
|
-
|
133
|
+
end
|
112
134
|
|
113
135
|
private
|
114
136
|
|
@@ -116,7 +138,7 @@ class BusSchemeParserTest < Test::Unit::TestCase
|
|
116
138
|
assert_equal expected, BusScheme.parse(actual_string)
|
117
139
|
end
|
118
140
|
|
119
|
-
def assert_parses_equal(one, two)
|
120
|
-
assert_equal BusScheme.parse(one), BusScheme.parse(two)
|
141
|
+
def assert_parses_equal(one, two, message = nil)
|
142
|
+
assert_equal BusScheme.parse(one), BusScheme.parse(two), message
|
121
143
|
end
|
122
144
|
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class PrimitivesTest < Test::Unit::TestCase
|
5
|
+
def test_booleans
|
6
|
+
assert BusScheme::PRIMITIVES.has_key? '#f'.intern
|
7
|
+
assert_evals_to false, '#f'
|
8
|
+
assert_evals_to true, '#t'
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_arithmetic
|
12
|
+
assert_evals_to 2, [:'-', 4, 2]
|
13
|
+
assert_evals_to 2, [:'/', 4, 2]
|
14
|
+
assert_evals_to 2, [:'*', 1, 2]
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_many_args_for_arithmetic
|
18
|
+
assert_evals_to 4, [:+, 1, 1, 1, 1]
|
19
|
+
assert_evals_to 2, [:*, 1, 2, 1, 1]
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_concat
|
23
|
+
assert_evals_to "foobar", "(concat \"foo\" \"bar\")"
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_eval
|
27
|
+
assert_evals_to 23, "(eval '(+ 20 3))"
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_eval_ruby
|
31
|
+
assert_evals_to "foofoofoo", "(ruby \"'foo' * 3\")"
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_message_passing
|
35
|
+
assert_evals_to 7, "(send 3 (intern \"+\") 4)"
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_load_file
|
39
|
+
eval "(load \"#{File.dirname(__FILE__)}/foo.scm\")"
|
40
|
+
assert_evals_to 3, :foo
|
41
|
+
end
|
42
|
+
|
43
|
+
# special forms
|
44
|
+
def test_define
|
45
|
+
clear_symbols :foo
|
46
|
+
eval("(define foo 5)")
|
47
|
+
assert_equal 5, BusScheme::Lambda.scope[:foo]
|
48
|
+
eval("(define foo (quote (5 5 5))")
|
49
|
+
assert_evals_to [5, 5, 5].to_list, :foo
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_define_returns_defined_term
|
53
|
+
assert_evals_to :foo, "(define foo 2)"
|
54
|
+
assert_equal 2, eval("foo")
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_eval_quote
|
58
|
+
assert_evals_to [:'+', 2, 2].to_list, [:quote, [:'+', 2, 2]]
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_quote
|
62
|
+
assert_evals_to :hi, [:quote, :hi]
|
63
|
+
assert_evals_to [:a, :b, :c].to_list, "'(a b c)"
|
64
|
+
assert_evals_to [:a].to_list, "(list 'a)"
|
65
|
+
assert_evals_to [:a, :b].to_list, "(list 'a 'b)"
|
66
|
+
assert_evals_to [:a, :b, :c].to_list, "(list 'a 'b 'c)"
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_if
|
70
|
+
assert_evals_to 7, [:if, '#f'.intern, 3, 7]
|
71
|
+
assert_evals_to 3, [:if, [:>, 8, 2], 3, 7]
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_begin
|
75
|
+
eval([:begin,
|
76
|
+
[:define, :foo, 779],
|
77
|
+
9])
|
78
|
+
assert_equal 779, BusScheme::SYMBOL_TABLE[:foo]
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_set!
|
82
|
+
clear_symbols(:foo)
|
83
|
+
# can only set! existing variables
|
84
|
+
assert_raises(BusScheme::EvalError) { eval "(set! foo 7)" }
|
85
|
+
eval "(define foo 3)"
|
86
|
+
eval "(set! foo 7)"
|
87
|
+
assert_evals_to 7, :foo
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_consing
|
91
|
+
assert_evals_to BusScheme::Cons.new(:foo, :bar), "(cons (quote foo) (quote bar))"
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_vectors
|
95
|
+
assert_evals_to [1, 2, 3], "#(1 2 3)"
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_inspect
|
99
|
+
assert_equal "(1)", [1].to_list.inspect
|
100
|
+
assert_equal "(1 . 1)", BusScheme::Cons.new(1, 1).inspect
|
101
|
+
assert_equal "(1 1 1)", BusScheme::Cons.new(1, BusScheme::Cons.new(1, BusScheme::Cons.new(1))).inspect
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_let
|
105
|
+
assert_evals_to 4, "(let ((x 2)
|
106
|
+
(y 2))
|
107
|
+
(+ x y))"
|
108
|
+
|
109
|
+
assert_evals_to 6, "(let ((doubler (lambda (x) (* 2 x)))
|
110
|
+
(x 3))
|
111
|
+
(doubler x))"
|
112
|
+
|
113
|
+
assert_evals_to 6, "(let ((doubler (lambda (x) (* 2 x)))
|
114
|
+
(x 3))
|
115
|
+
x
|
116
|
+
(doubler x))"
|
117
|
+
end
|
118
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bus-scheme
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Phil Hagelberg
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-01-
|
12
|
+
date: 2008-01-30 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -38,18 +38,22 @@ files:
|
|
38
38
|
- bin/bus
|
39
39
|
- lib/array_extensions.rb
|
40
40
|
- lib/bus_scheme.rb
|
41
|
+
- lib/cons.rb
|
41
42
|
- lib/eval.rb
|
42
43
|
- lib/lambda.rb
|
43
|
-
- lib/definitions.rb
|
44
44
|
- lib/object_extensions.rb
|
45
45
|
- lib/parser.rb
|
46
|
+
- lib/primitives.rb
|
47
|
+
- lib/scheme/core.scm
|
46
48
|
- test/foo.scm
|
49
|
+
- test/test_core.rb
|
47
50
|
- test/test_eval.rb
|
48
51
|
- test/test_helper.rb
|
49
52
|
- test/test_lambda.rb
|
50
53
|
- test/test_parser.rb
|
54
|
+
- test/test_primitives.rb
|
51
55
|
has_rdoc: true
|
52
|
-
homepage: " by Phil Hagelberg (c) 2007"
|
56
|
+
homepage: " by Phil Hagelberg (c) 2007 - 2008"
|
53
57
|
post_install_message:
|
54
58
|
rdoc_options:
|
55
59
|
- --main
|
@@ -77,7 +81,8 @@ specification_version: 2
|
|
77
81
|
summary: Bus Scheme is a Scheme in Ruby, imlemented on the bus.
|
78
82
|
test_files:
|
79
83
|
- test/test_lambda.rb
|
80
|
-
- test/
|
84
|
+
- test/test_core.rb
|
85
|
+
- test/test_primitives.rb
|
81
86
|
- test/test_parser.rb
|
82
87
|
- test/test_eval.rb
|
83
88
|
- test/test_helper.rb
|
data/lib/definitions.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
module BusScheme
|
2
|
-
class BusSchemeError < StandardError; end
|
3
|
-
class ParseError < BusSchemeError; end
|
4
|
-
class EvalError < BusSchemeError; end
|
5
|
-
class ArgumentError < BusSchemeError; end
|
6
|
-
|
7
|
-
PRIMITIVES = {
|
8
|
-
'#t'.intern => true, # :'#t' screws up emacs' ruby parser
|
9
|
-
'#f'.intern => false,
|
10
|
-
|
11
|
-
:+ => lambda { |*args| args.inject(0) { |sum, i| sum + i } },
|
12
|
-
:- => lambda { |x, y| x - y },
|
13
|
-
:* => lambda { |*args| args.inject(1) { |product, i| product * i } },
|
14
|
-
'/'.intern => lambda { |x, y| x / y },
|
15
|
-
|
16
|
-
:> => lambda { |x, y| x > y },
|
17
|
-
:< => lambda { |x, y| x < y },
|
18
|
-
:'=' => lambda { |x, y| x == y }, # may not honor scheme's equality notions
|
19
|
-
:null? => lambda { |x| x.nil? },
|
20
|
-
|
21
|
-
:intern => lambda { |x| x.intern },
|
22
|
-
:substring => lambda { |x, from, to| x[from .. to] },
|
23
|
-
|
24
|
-
:car => lambda { |list| list.car },
|
25
|
-
:cdr => lambda { |list| list.cdr },
|
26
|
-
|
27
|
-
:ruby => lambda { |code| eval(code) },
|
28
|
-
:load => lambda { |filename| eval_string(File.read(filename)) },
|
29
|
-
:exit => lambda { exit }, :quit => lambda { exit },
|
30
|
-
}
|
31
|
-
|
32
|
-
# if we add in macros, can some of these be defined in scheme?
|
33
|
-
SPECIAL_FORMS = {
|
34
|
-
:quote => lambda { |arg| arg },
|
35
|
-
# TODO: check that nil, () and #f all behave according to spec
|
36
|
-
:if => lambda { |q, yes, *no| eval_form(q) ? eval_form(yes) : eval_form([:begin] + no) },
|
37
|
-
:begin => lambda { |*args| args.map{ |arg| eval_form(arg) }.last },
|
38
|
-
:set! => lambda { |sym, value| BusScheme[sym] and
|
39
|
-
BusScheme[sym] = eval_form(value); sym },
|
40
|
-
:lambda => lambda { |args, *form| Lambda.new(args, form) },
|
41
|
-
:define => lambda { |sym, definition| BusScheme[sym] = eval_form(definition); sym },
|
42
|
-
}
|
43
|
-
end
|
data/test/test_compiler.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
-
require 'test_helper'
|
3
|
-
|
4
|
-
# must be run in Rubinius
|
5
|
-
# how to check?
|
6
|
-
|
7
|
-
class BusSchemeCompilerTest < Test::Unit::TestCase
|
8
|
-
# def test_compiles_arithmetic
|
9
|
-
# assert_equivalent "(+ 1 2)", "1 + 2"
|
10
|
-
# end
|
11
|
-
|
12
|
-
private
|
13
|
-
def assert_equivalent(scheme, ruby)
|
14
|
-
scheme = BusScheme.parse(scheme) if scheme.is_a? String # for convenience
|
15
|
-
parsed_ruby = ruby.to_sexp.last
|
16
|
-
compiled_scheme = BusScheme::Compiler.compile(scheme)
|
17
|
-
assert_equal
|
18
|
-
end
|
19
|
-
end
|