bus-scheme 0.7.1 → 0.7.5
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/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
|