shen-ruby 0.3.1 → 0.4.0

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.
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