shen-ruby 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +2 -0
  2. data/.travis.yml +5 -0
  3. data/Gemfile +2 -2
  4. data/HISTORY.md +12 -0
  5. data/README.md +10 -7
  6. data/Rakefile +92 -0
  7. data/bin/srrepl +2 -2
  8. data/k_lambda_spec/primitives/arithmetic_spec.rb +175 -0
  9. data/k_lambda_spec/primitives/assignments_spec.rb +44 -0
  10. data/k_lambda_spec/primitives/generic_functions_spec.rb +115 -2
  11. data/k_lambda_spec/primitives/lists_spec.rb +40 -0
  12. data/k_lambda_spec/primitives/strings_spec.rb +77 -0
  13. data/k_lambda_spec/primitives/symbols_spec.rb +24 -0
  14. data/k_lambda_spec/primitives/vectors_spec.rb +92 -0
  15. data/k_lambda_spec/support/shared_examples.rb +93 -2
  16. data/k_lambda_spec/tail_recursion_spec.rb +30 -0
  17. data/lib/kl/compiler.rb +19 -33
  18. data/lib/kl/environment.rb +1 -0
  19. data/lib/kl/primitives/assignments.rb +1 -0
  20. data/lib/kl/primitives/generic_functions.rb +7 -0
  21. data/lib/kl/primitives/lists.rb +2 -0
  22. data/lib/kl/primitives/strings.rb +13 -5
  23. data/lib/kl/primitives/symbols.rb +1 -0
  24. data/lib/kl/primitives/vectors.rb +5 -0
  25. data/lib/shen_ruby/version.rb +1 -1
  26. data/shen-ruby.gemspec +1 -1
  27. data/shen/lib/shen_ruby/shen.rb +5 -6
  28. data/shen/release/benchmarks/benchmarks.shen +0 -4
  29. data/shen/release/benchmarks/interpreter.shen +2 -2
  30. data/shen/release/benchmarks/plato.jpg +0 -0
  31. data/shen/release/k_lambda/core.kl +171 -1000
  32. data/shen/release/k_lambda/declarations.kl +90 -992
  33. data/shen/release/k_lambda/load.kl +69 -81
  34. data/shen/release/k_lambda/macros.kl +113 -478
  35. data/shen/release/k_lambda/prolog.kl +250 -1307
  36. data/shen/release/k_lambda/reader.kl +115 -996
  37. data/shen/release/k_lambda/sequent.kl +154 -554
  38. data/shen/release/k_lambda/sys.kl +246 -562
  39. data/shen/release/k_lambda/t-star.kl +114 -3643
  40. data/shen/release/k_lambda/toplevel.kl +136 -221
  41. data/shen/release/k_lambda/track.kl +101 -206
  42. data/shen/release/k_lambda/types.kl +143 -298
  43. data/shen/release/k_lambda/writer.kl +93 -106
  44. data/shen/release/k_lambda/yacc.kl +77 -252
  45. data/shen/release/test_programs/README.shen +1 -1
  46. data/shen/release/test_programs/classes-typed.shen +1 -1
  47. data/shen/release/test_programs/interpreter.shen +2 -2
  48. data/shen/release/test_programs/metaprog.shen +2 -2
  49. data/shen/release/test_programs/prolog.shen +79 -0
  50. data/shen/release/test_programs/structures-typed.shen +2 -2
  51. data/shen/release/test_programs/tests.shen +19 -80
  52. data/shen/release/test_programs/yacc.shen +11 -15
  53. metadata +14 -6
  54. data/Gemfile.lock +0 -20
  55. data/shen/release/benchmarks/br.shen +0 -13
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Primitives for lists' do
4
+ describe '(cons Hd Tl)' do
5
+ it 'creates a list with Hd as the head and Tl as the tail' do
6
+ kl_eval('(cons a ())').should be_kind_of Kl::Cons
7
+ kl_eval('(hd (cons a ()))').should == :a
8
+ kl_eval('(tl (cons a (cons b ())))').should == kl_eval('(cons b ())')
9
+ end
10
+
11
+ it 'allows Tl to be a non-list' do
12
+ kl_eval('(tl (cons a b))').should == :b
13
+ end
14
+
15
+ include_examples 'partially-applicable function', %w(cons a b)
16
+ include_examples 'applicative order evaluation', %w(cons a b)
17
+ end
18
+
19
+ describe '(hd L)' do
20
+ it 'returns the head of L' do
21
+ kl_eval('(hd (cons a b))').should == :a
22
+ end
23
+
24
+ include_examples 'argument types', [:hd, "(cons a b)"], 1 => [:list, :dotted_pair]
25
+ include_examples 'partially-applicable function', [:hd, "(cons a b)"]
26
+ end
27
+
28
+ describe '(tl L)' do
29
+ it 'returns the tail of L' do
30
+ kl_eval('(tl (cons a b))').should == :b
31
+ end
32
+
33
+ include_examples 'argument types', [:tl, "(cons a b)"], 1 => [:list, :dotted_pair]
34
+ include_examples 'partially-applicable function', [:tl, "(cons a b)"]
35
+ end
36
+
37
+ describe 'cons?' do
38
+ include_examples 'type predicate', 'cons?', [:list, :dotted_pair]
39
+ end
40
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Primitives for strings' do
4
+ describe '(pos S N)' do
5
+ it 'returns the character at zero-based index N of S as a unit string' do
6
+ kl_eval('(pos "ABC" 1)').should == "B"
7
+ end
8
+
9
+ it 'raises an error if N is negative' do
10
+ expect {
11
+ kl_eval('(pos "ABC" -1)')
12
+ }.to raise_error(Kl::Error, "out of bounds")
13
+ end
14
+
15
+ it 'raises an error if N is >= the length of S' do
16
+ expect {
17
+ kl_eval('(pos "ABC" 3)')
18
+ }.to raise_error(Kl::Error, "out of bounds")
19
+ expect {
20
+ kl_eval('(pos "ABC" 99)')
21
+ }.to raise_error(Kl::Error, "out of bounds")
22
+ end
23
+
24
+ include_examples 'argument types', %w(pos "string" 1),
25
+ 1 => [:string],
26
+ 2 => [:integer]
27
+ include_examples 'partially-applicable function', %w(pos "string" 1)
28
+ include_examples 'applicative order evaluation', %w(pos "string" 1)
29
+ end
30
+
31
+ describe '(tlstr S)' do
32
+ it 'returns a string containing all but the first character of S' do
33
+ kl_eval('(tlstr "string")').should == "tring"
34
+ end
35
+
36
+ it 'raises an error when S is the empty string' do
37
+ expect {
38
+ kl_eval('(tlstr "")')
39
+ }.to raise_error(Kl::Error, 'attempted to take tail of an empty string')
40
+ end
41
+
42
+ include_examples 'argument types', %w(tlstr "string"), 1 => [:string]
43
+ include_examples 'partially-applicable function', %w(tlstr "string")
44
+ end
45
+
46
+ describe 'string?' do
47
+ include_examples 'type predicate', 'string?', [:string]
48
+ end
49
+
50
+ describe '(n->string N)' do
51
+ it 'returns a unit string containing the character with ASCII code N' do
52
+ kl_eval('(n->string 65)').should == "A"
53
+ end
54
+
55
+ include_examples 'argument types', %w(n->string 65), 1 => [:integer]
56
+ include_examples 'partially-applicable function', %w(n->string 65)
57
+ end
58
+
59
+ describe '(string->n S)' do
60
+ it 'returns the ASCII code of the unit string S' do
61
+ kl_eval('(string->n "A")').should == 65
62
+ end
63
+
64
+ it 'returns the ASCII code of the first character of non-unit string S' do
65
+ kl_eval('(string->n "AB")').should == 65
66
+ end
67
+
68
+ it 'raises an error when S is the empty string' do
69
+ expect {
70
+ kl_eval('(string->n "")')
71
+ }.to raise_error(Kl::Error, 'attempted to get code point of empty string')
72
+ end
73
+
74
+ include_examples 'argument types', %w(string->n "A"), 1 => [:string]
75
+ include_examples 'partially-applicable function', %w(string->n "A")
76
+ end
77
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Primitives for symbols' do
4
+ describe 'intern' do
5
+ it 'converts a string to its corresponding symbol' do
6
+ kl_eval('(intern "foo")').should == :foo
7
+ end
8
+
9
+ it 'supports characters not allowed in symbol literals' do
10
+ kl_eval('(intern "[{|}]")').should == :"[{|}]"
11
+ end
12
+
13
+ it 'converts the string "true" to boolean true' do
14
+ kl_eval('(intern "true")').should == true
15
+ end
16
+
17
+ it 'converts the string "false" to boolean false' do
18
+ kl_eval('(intern "false")').should == false
19
+ end
20
+
21
+ include_examples 'argument types', %w(intern "foo"), 1 => [:string]
22
+ include_examples 'partially-applicable function', %w(intern "foo")
23
+ end
24
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Primitive functions for vectors' do
4
+ describe '(absvector N)' do
5
+ it 'returns a new absolute vector of size N' do
6
+ kl_eval('(absvector 3)').should be_kind_of Kl::Absvector
7
+ end
8
+
9
+ include_examples 'argument types', %w(absvector 7), 1 => [:integer]
10
+ include_examples 'partially-applicable function', %w(absvector 7)
11
+ end
12
+
13
+ describe '(address-> V N Value)' do
14
+ before(:each) do
15
+ kl_eval('(set *vec* (absvector 5))')
16
+ end
17
+
18
+ it 'returns the vector V updated with Value at index N' do
19
+ kl_eval('(address-> (value *vec*) 3 37)').should be_kind_of Kl::Absvector
20
+ kl_eval('(<-address (value *vec*) 3)').should == 37
21
+ end
22
+
23
+ it 'raises an error if N is negative' do
24
+ expect {
25
+ kl_eval('(address-> (value *vec*) -1 37)')
26
+ }.to raise_error(Kl::Error, "out of bounds")
27
+ end
28
+
29
+ it 'raises an error if N is >= the size of the vector' do
30
+ expect {
31
+ kl_eval('(address-> (value *vec*) 5 37)')
32
+ }.to raise_error(Kl::Error, "out of bounds")
33
+ expect {
34
+ kl_eval('(address-> (value *vec*) 99 37)')
35
+ }.to raise_error(Kl::Error, "out of bounds")
36
+ end
37
+
38
+ include_examples 'argument types',
39
+ ['address->', '(value *vec*)', '0', '37'],
40
+ 1 => [:vector],
41
+ 2 => [:integer]
42
+ include_examples 'partially-applicable function',
43
+ ['address->', '(value *vec*)', '0', '37']
44
+ include_examples 'applicative order evaluation',
45
+ ['address->', '(value *vec*)', '0', '37']
46
+ end
47
+
48
+ describe '(<-address V N)' do
49
+ before(:each) do
50
+ kl_eval('(set *vec* (absvector 5))')
51
+ end
52
+
53
+ it 'returns the value previously stored at index N in V' do
54
+ kl_eval('(address-> (value *vec*) 3 37)')
55
+ kl_eval('(<-address (value *vec*) 3)').should == 37
56
+ end
57
+
58
+ it 'returns an unspecified value if index N has not been stored to' do
59
+ expect {
60
+ kl_eval('(<-address (value *vec*) 3)')
61
+ }.to_not raise_error
62
+ end
63
+
64
+ it 'raises an error if N is negative' do
65
+ expect {
66
+ kl_eval('(<-address (value *vec*) -1)')
67
+ }.to raise_error(Kl::Error, "out of bounds")
68
+ end
69
+
70
+ it 'raises an error if N is >= the size of the vector' do
71
+ expect {
72
+ kl_eval('(<-address (value *vec*) 5)')
73
+ }.to raise_error(Kl::Error, "out of bounds")
74
+ expect {
75
+ kl_eval('(<-address (value *vec*) 99)')
76
+ }.to raise_error(Kl::Error, "out of bounds")
77
+ end
78
+
79
+ include_examples 'argument types',
80
+ ['<-address', '(value *vec*)', '0'],
81
+ 1 => [:vector],
82
+ 2 => [:integer]
83
+ include_examples 'partially-applicable function',
84
+ ['address->', '(value *vec*)', '0', '37']
85
+ include_examples 'applicative order evaluation',
86
+ ['address->', '(value *vec*)', '0', '37']
87
+ end
88
+
89
+ describe 'absvector?' do
90
+ include_examples 'type predicate', 'absvector?', [:vector]
91
+ end
92
+ end
@@ -1,7 +1,7 @@
1
1
  # args should be an array containing the components of an example expression.
2
2
  # E.g., to test partial application of +, you could use:
3
3
  #
4
- # include_examples "a partially-applicable function", %w(+ 1 2)
4
+ # include_examples "partially-applicable function", %w(+ 1 2)
5
5
  #
6
6
  # The expression will be evaluated in its fully-expanded form for reference
7
7
  # and then compared against various partial application scenarios.
@@ -20,7 +20,7 @@ end
20
20
 
21
21
  shared_examples "non-partially-applicable function" do |args|
22
22
  (0...(args.length - 1)).each do |arg_count|
23
- description = "raises an error when applied to #{arg_count} argument"
23
+ description = "raises an error when given #{arg_count} argument"
24
24
  description << "s" unless arg_count == 1
25
25
  it description do
26
26
  partial_expression = "(#{args[0..arg_count].join(' ')})"
@@ -29,5 +29,96 @@ shared_examples "non-partially-applicable function" do |args|
29
29
  }.to raise_error(Kl::Error, "#{args[0]} expects #{args.length - 1} arguments but was given #{arg_count}")
30
30
  end
31
31
  end
32
+ end
33
+
34
+ # args should be an array containing the components of an example expression.
35
+ # E.g., to test applicative order evaluation of +, you could use:
36
+ #
37
+ # include_examples "applicative order evaluation", %w(+ 1 2)
38
+ shared_examples 'applicative order evaluation' do |args|
39
+ it 'evaluates its arguments from left to right' do
40
+ operator = args.shift
41
+ arg_indexes = (0...args.size).to_a
42
+ instrumented_args = args.zip(arg_indexes).map do |(arg, idx)|
43
+ "(kl-do (set *arg-order* (cn (value *arg-order*) \"#{idx}\")) #{arg})"
44
+ end
45
+ expected = arg_indexes.join
46
+
47
+ define_kl_do
48
+ kl_eval('(set *arg-order* "")')
49
+ kl_eval("(#{operator} #{instrumented_args.join(' ')})")
50
+ kl_eval('(value *arg-order*)').should == expected
51
+ end
52
+ end
53
+
54
+ KL_TYPE_EXAMPLES = {
55
+ integer: '1',
56
+ real: '1.0',
57
+ string: '"a string"',
58
+ symbol: 'a-symbol',
59
+ boolean: 'true',
60
+ list: '(cons 1 ())',
61
+ dotted_pair: '(cons 1 2)',
62
+ empty_list: '()',
63
+ function: '(lambda X X)',
64
+ vector: '(absvector 3)'
65
+ }
66
+
67
+ def type_with_article(type)
68
+ name = type.to_s.gsub(/_/, ' ')
69
+ if [:integer, :empty_list].include?(type)
70
+ 'an ' + name
71
+ else
72
+ 'a ' + name
73
+ end
74
+ end
75
+
76
+ shared_examples 'type predicate' do |predicate, accepted_types|
77
+ accepted_types.each do |type|
78
+ it "returns true when its argument is #{type_with_article(type)}" do
79
+ kl_eval("(#{predicate} #{KL_TYPE_EXAMPLES[type]})").should == true
80
+ end
81
+ end
82
+
83
+ it 'returns false when its argument is of any other type' do
84
+ rejected_types = KL_TYPE_EXAMPLES.keys - accepted_types
85
+ rejected_types.each do |type|
86
+ kl_eval("(#{predicate} #{KL_TYPE_EXAMPLES[type]})").should == false
87
+ end
88
+ end
89
+ end
90
+
91
+ POSITION_NAMES = { 1 => 'first', 2 => 'second', 3 => 'third' }
32
92
 
93
+ def types_to_s(types)
94
+ if types.length > 2
95
+ types[0..-2].map { |t| type_with_article(t) }.join(', ') +
96
+ ', or ' + type_with_article(types[-1])
97
+ else
98
+ types.map { |t| type_with_article(t) }.join(' or ')
99
+ end
100
+ end
101
+
102
+ shared_examples 'argument types' do |expr, accepted_argument_types|
103
+ accepted_argument_types.to_a.sort.each do |(idx, accepted_types)|
104
+ type_str = types_to_s(accepted_types)
105
+ it "raises an error if its #{POSITION_NAMES[idx]} argument is not #{type_str}" do
106
+ accepted_types.each do |type|
107
+ other_expr = expr.dup
108
+ other_expr[idx] = KL_TYPE_EXAMPLES[type]
109
+ expect {
110
+ kl_eval("(#{other_expr.join(' ')})")
111
+ }.to_not raise_error
112
+ end
113
+
114
+ rejected_types = KL_TYPE_EXAMPLES.keys - accepted_types
115
+ rejected_types.each do |type|
116
+ other_expr = expr.dup
117
+ other_expr[idx] = KL_TYPE_EXAMPLES[type]
118
+ expect {
119
+ kl_eval("(#{other_expr.join(' ')})")
120
+ }.to raise_error(Kl::Error, /is not a/)
121
+ end
122
+ end
123
+ end
33
124
  end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Tail recursion' do
4
+ it 'does not consume stack space for self tail calls' do
5
+ kl_eval <<-EOS
6
+ (defun count-down (X)
7
+ (if (= X 0)
8
+ success
9
+ (count-down (- X 1))))
10
+ EOS
11
+ kl_eval('(count-down 10000)').should == :success
12
+ end
13
+
14
+ it 'does not consume stack space for mutually recursive tail calls' do
15
+ kl_eval <<-EOS
16
+ (defun even? (X)
17
+ (if (= X 1)
18
+ false
19
+ (odd? (- X 1))))
20
+ EOS
21
+ kl_eval <<-EOS
22
+ (defun odd? (X)
23
+ (if (= X 1)
24
+ true
25
+ (even? (- X 1))))
26
+ EOS
27
+ kl_eval('(even? 100000)').should == true
28
+ kl_eval('(odd? 100000)').should == false
29
+ end
30
+ end
@@ -59,7 +59,7 @@ module Kl
59
59
  when :let
60
60
  compile_let(form, lexical_vars, in_tail_pos)
61
61
  when :freeze
62
- compile_freeze(form, lexical_vars)
62
+ compile_freeze(form, lexical_vars, in_tail_pos)
63
63
  when :type
64
64
  compile_type(form, lexical_vars, in_tail_pos)
65
65
  when :if
@@ -78,10 +78,6 @@ module Kl
78
78
  # when all of their arguments are available.
79
79
  when :cons
80
80
  compile_cons(form, lexical_vars, in_tail_pos)
81
- when :hd
82
- compile_hd(form, lexical_vars, in_tail_pos)
83
- when :tl
84
- compile_tl(form, lexical_vars, in_tail_pos)
85
81
  when :"cons?"
86
82
  compile_consp(form, lexical_vars, in_tail_pos)
87
83
  else
@@ -93,10 +89,15 @@ module Kl
93
89
  def compile_defun(form, lexical_vars)
94
90
  name, arglist, body = destructure_form(form, 3)
95
91
  unless name.kind_of? Symbol
96
- raise Kl::Error, 'first argument to defun must be a symbol'
92
+ raise Kl::Error, "#{name} is not a symbol"
97
93
  end
98
- unless arglist.all? {|a| a.kind_of? Symbol}
99
- raise Kl::Error, 'function argument list may only contain symbols'
94
+ unless [Kl::Cons, Kl::EmptyList].include? arglist.class
95
+ raise Kl::Error, "#{arglist} is not a list"
96
+ end
97
+ arglist.each do |arg|
98
+ unless arg.kind_of? Symbol
99
+ raise Kl::Error, "#{arg} is not a symbol"
100
+ end
100
101
  end
101
102
  if PRIMITIVE_ARITIES.has_key?(name)
102
103
  raise Kl::Error, "#{name} is primitive and may not be redefined"
@@ -115,7 +116,7 @@ module Kl
115
116
  def compile_lambda(form, lexical_vars)
116
117
  var, body = destructure_form(form, 2)
117
118
  unless var.kind_of? Symbol
118
- raise Kl::Error, 'first argument to lambda must be a symbol'
119
+ raise Kl::Error, "#{var} is not a symbol"
119
120
  end
120
121
 
121
122
  extended_vars = add_var(lexical_vars, var)
@@ -142,12 +143,17 @@ module Kl
142
143
  end
143
144
 
144
145
  # (freeze EXPR)
145
- def compile_freeze(form, lexical_vars)
146
- expr = destructure_form(form, 1).first
146
+ def compile_freeze(form, lexical_vars, in_tail_pos)
147
+ if form.count == 2
148
+ expr = destructure_form(form, 1).first
147
149
 
148
- body = compile(expr, lexical_vars, true)
150
+ body = compile(expr, lexical_vars, true)
149
151
 
150
- "::Kernel.lambda { #{body} }"
152
+ "::Kernel.lambda { #{body} }"
153
+ else
154
+ # Partial application falls back to normal application
155
+ compile_application(form, lexical_vars, in_tail_pos)
156
+ end
151
157
  end
152
158
 
153
159
  # (type EXPR T)
@@ -257,26 +263,6 @@ module Kl
257
263
  end
258
264
  end
259
265
 
260
- # Inlined version of (hd L)
261
- def compile_hd(form, lexical_vars, in_tail_pos)
262
- if form.count == 2
263
- expr = compile(form.tl.hd, lexical_vars, false)
264
- "(#{expr}).hd"
265
- else
266
- compile_application(form, lexical_vars, in_tail_pos)
267
- end
268
- end
269
-
270
- # Inlined version of (tl L)
271
- def compile_tl(form, lexical_vars, in_tail_pos)
272
- if form.count == 2
273
- expr = compile(form.tl.hd, lexical_vars, false)
274
- "(#{expr}).tl"
275
- else
276
- compile_application(form, lexical_vars, in_tail_pos)
277
- end
278
- end
279
-
280
266
  # Inlined version of (cons? X)
281
267
  def compile_consp(form, lexical_vars, in_tail_pos)
282
268
  if form.count == 2