klam 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +0 -0
  4. data/.travis.yml +8 -0
  5. data/Gemfile +1 -0
  6. data/LICENSE.txt +19 -0
  7. data/PROGRESS.asciidoc +105 -0
  8. data/README.asciidoc +11 -0
  9. data/Rakefile +5 -0
  10. data/klam.gemspec +28 -0
  11. data/lib/klam.rb +16 -0
  12. data/lib/klam/compilation_stages.rb +4 -0
  13. data/lib/klam/compilation_stages/convert_freezes_to_lambdas.rb +17 -0
  14. data/lib/klam/compilation_stages/convert_lexical_variables.rb +79 -0
  15. data/lib/klam/compilation_stages/convert_partial_applications_to_lambdas.rb +35 -0
  16. data/lib/klam/compilation_stages/convert_self_tail_calls_to_loops.rb +147 -0
  17. data/lib/klam/compilation_stages/curry_abstraction_applications.rb +33 -0
  18. data/lib/klam/compilation_stages/emit_ruby.rb +232 -0
  19. data/lib/klam/compilation_stages/kl_to_internal_representation.rb +21 -0
  20. data/lib/klam/compilation_stages/make_abstractions_monadic.rb +26 -0
  21. data/lib/klam/compilation_stages/make_abstractions_variadic.rb +23 -0
  22. data/lib/klam/compilation_stages/simplify_boolean_operations.rb +74 -0
  23. data/lib/klam/compilation_stages/strip_type_declarations.rb +24 -0
  24. data/lib/klam/compiler.rb +63 -0
  25. data/lib/klam/cons.rb +18 -0
  26. data/lib/klam/converters.rb +4 -0
  27. data/lib/klam/converters/.list.rb.swp +0 -0
  28. data/lib/klam/converters/list.rb +29 -0
  29. data/lib/klam/environment.rb +61 -0
  30. data/lib/klam/error.rb +4 -0
  31. data/lib/klam/lexer.rb +185 -0
  32. data/lib/klam/primitives.rb +4 -0
  33. data/lib/klam/primitives/arithmetic.rb +49 -0
  34. data/lib/klam/primitives/assignments.rb +13 -0
  35. data/lib/klam/primitives/boolean_operations.rb +22 -0
  36. data/lib/klam/primitives/error_handling.rb +19 -0
  37. data/lib/klam/primitives/generic_functions.rb +19 -0
  38. data/lib/klam/primitives/lists.rb +23 -0
  39. data/lib/klam/primitives/streams.rb +32 -0
  40. data/lib/klam/primitives/strings.rb +58 -0
  41. data/lib/klam/primitives/symbols.rb +16 -0
  42. data/lib/klam/primitives/time.rb +19 -0
  43. data/lib/klam/primitives/vectors.rb +26 -0
  44. data/lib/klam/reader.rb +46 -0
  45. data/lib/klam/template.rb +38 -0
  46. data/lib/klam/variable.rb +21 -0
  47. data/lib/klam/variable_generator.rb +12 -0
  48. data/lib/klam/version.rb +3 -0
  49. data/spec/functional/application_spec.rb +94 -0
  50. data/spec/functional/atoms_spec.rb +56 -0
  51. data/spec/functional/extensions/do_spec.rb +22 -0
  52. data/spec/functional/primitives/assignments_spec.rb +38 -0
  53. data/spec/functional/primitives/boolean_operations_spec.rb +133 -0
  54. data/spec/functional/primitives/error_handling_spec.rb +22 -0
  55. data/spec/functional/primitives/generic_functions_spec.rb +82 -0
  56. data/spec/functional/tail_call_optimization_spec.rb +71 -0
  57. data/spec/spec_helper.rb +27 -0
  58. data/spec/unit/klam/compilation_stages/convert_lexical_variables_spec.rb +58 -0
  59. data/spec/unit/klam/compilation_stages/convert_self_tail_calls_to_loops_spec.rb +33 -0
  60. data/spec/unit/klam/compilation_stages/curry_abstraction_applications_spec.rb +19 -0
  61. data/spec/unit/klam/compilation_stages/make_abstractions_variadic_spec.rb +12 -0
  62. data/spec/unit/klam/converters/list_spec.rb +57 -0
  63. data/spec/unit/klam/lexer_spec.rb +149 -0
  64. data/spec/unit/klam/primitives/arithmetic_spec.rb +153 -0
  65. data/spec/unit/klam/primitives/boolean_operations_spec.rb +39 -0
  66. data/spec/unit/klam/primitives/error_handling_spec.rb +19 -0
  67. data/spec/unit/klam/primitives/lists_spec.rb +49 -0
  68. data/spec/unit/klam/primitives/strings_spec.rb +53 -0
  69. data/spec/unit/klam/primitives/symbols_spec.rb +19 -0
  70. data/spec/unit/klam/primitives/time_spec.rb +16 -0
  71. data/spec/unit/klam/primitives/vectors_spec.rb +55 -0
  72. data/spec/unit/klam/reader_spec.rb +47 -0
  73. data/spec/unit/klam/template_spec.rb +28 -0
  74. data/spec/unit/klam/variable_spec.rb +22 -0
  75. data/spec/unit/klam/version_spec.rb +7 -0
  76. metadata +225 -0
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klam::CompilationStages::ConvertSelfTailCallsToLoops do
4
+ include Klam::CompilationStages::ConvertSelfTailCallsToLoops
5
+ it 'converts self tail calls to loop/recur' do
6
+ # For simplicity the vars below are symbols, but will be
7
+ # Klam::Variables in practice. They do not come into play for
8
+ # the conversion, though.
9
+ expr = [:defun, :f, [:X, :Y],
10
+ [:if, true,
11
+ [:f, 7, 8],
12
+ [:let, :Z, 37,
13
+ [:f, :Z, 99]]]]
14
+ expected = [:defun, :f, [:X, :Y],
15
+ [:"[LOOP]", :f, [:X, :Y],
16
+ [:if, true,
17
+ [:"[RECUR]", [:X, :Y], [7, 8]],
18
+ [:let, :Z, 37,
19
+ [:"[RECUR]", [:X, :Y], [:Z, 99]]]]]]
20
+
21
+ expect(convert_self_tail_calls_to_loops(expr)).to eq(expected)
22
+ end
23
+
24
+ it 'does not convert partial calls' do
25
+ expr = [:defun, :f, [:X, :Y],
26
+ [:f, :X]]
27
+ expected = [:defun, :f, [:X, :Y],
28
+ [:"[LOOP]", :f, [:X, :Y],
29
+ [:f, :X]]]
30
+
31
+ expect(convert_self_tail_calls_to_loops(expr)).to eq(expected)
32
+ end
33
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klam::CompilationStages::CurryAbstractionApplications do
4
+ include Klam::CompilationStages::CurryAbstractionApplications
5
+ it 'converts to applying one argument at a time' do
6
+ expr = [[:foo], 1, 2, 3]
7
+ expected = [[[[:foo], 1], 2], 3]
8
+
9
+ expect(curry_abstraction_applications(expr)).to eq(expected)
10
+ end
11
+
12
+ it 'converts variable application to curried form' do
13
+ var = Klam::Variable.new('foo')
14
+ expr = [var, 1, 2, 3]
15
+ expected = [[[var, 1], 2], 3]
16
+
17
+ expect(curry_abstraction_applications(expr)).to eq(expected)
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klam::CompilationStages::MakeAbstractionsVariadic do
4
+ include Klam::CompilationStages::MakeAbstractionsVariadic
5
+
6
+ it 'converts the single Kl lambda param to a one-element list' do
7
+ expr = [:lambda, :X, [:lambda, :Y, [:+, :X, :Y]]]
8
+ expected = [:lambda, [:X], [:lambda, [:Y], [:+, :X, :Y]]]
9
+
10
+ expect(make_abstractions_variadic(expr)).to eq(expected)
11
+ end
12
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klam::Converters::List do
4
+ include Klam::Converters::List
5
+
6
+ before(:each) do
7
+ @empty_list = Klam::Primitives::Lists::EMPTY_LIST
8
+ end
9
+
10
+ describe '.arrayToList' do
11
+ it 'returns the empty list when given an empty array' do
12
+ expect(arrayToList([])).to be @empty_list
13
+ end
14
+
15
+ it 'handles nested empty lists' do
16
+ expect(arrayToList([[]])).to eq(cons(@empty_list, @empty_list))
17
+ end
18
+
19
+ it 'returns the corresponding list for a non-empty array' do
20
+ expect(arrayToList([1, 2])).to eq(cons(1, cons(2, @empty_list)))
21
+ end
22
+
23
+ it 'recursively converts nested arrays to nested lists' do
24
+ array1 = [1, 2]
25
+ list1 = arrayToList(array1)
26
+ array2 = [:a, :b, :c]
27
+ list2 = arrayToList(array2)
28
+
29
+ expect(arrayToList([array1, array2]))
30
+ .to eq(cons(list1, cons(list2, @empty_list)))
31
+ end
32
+ end
33
+
34
+ describe '.listToArray' do
35
+ it 'returns an empty array when given the empty list' do
36
+ expect(listToArray(@empty_list)).to eq([])
37
+ end
38
+
39
+ it 'handles nested empty lists' do
40
+ expect(listToArray(cons(@empty_list, @empty_list))).to eq([[]])
41
+ end
42
+
43
+ it 'returns the corresponding array for a non-emtpy list' do
44
+ expect(listToArray(cons(1, cons(2, @empty_list)))).to eq([1, 2])
45
+ end
46
+
47
+ it 'recursively converts nested lists to nested arrays' do
48
+ list1 = cons(1, cons(2, @empty_list))
49
+ array1 = listToArray(list1)
50
+ list2 = cons(:a, cons(:b, cons(:c, @empty_list)))
51
+ array2 = listToArray(list2)
52
+
53
+ expect(listToArray(cons(list1, cons(list2, @empty_list))))
54
+ .to eq([array1, array2])
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klam::Lexer do
4
+ def lexer(str)
5
+ Klam::Lexer.new(StringIO.new(str))
6
+ end
7
+
8
+ describe 'syntax tokens' do
9
+ it 'reads ( as a Klam::Lexer::OpenParen' do
10
+ expect(lexer("(").next).to be_kind_of(Klam::Lexer::OpenParen)
11
+ end
12
+
13
+ it 'reads ) as a Klam::Lexer::CloseParen' do
14
+ expect(lexer(")").next).to be_kind_of(Klam::Lexer::CloseParen)
15
+ end
16
+ end
17
+
18
+ describe 'string tokens' do
19
+ it 'reads double-quoted strings' do
20
+ expect(lexer('"Fred"').next).to eq("Fred")
21
+ end
22
+
23
+ it 'throws Klam::SyntaxError on unterminated strings' do
24
+ expect {
25
+ lexer('"Fred').next
26
+ }.to raise_error(Klam::SyntaxError, "unterminated string")
27
+ end
28
+ end
29
+
30
+ describe 'symbols' do
31
+ it 'reads sign characters not followed by digits as symbols' do
32
+ expect(lexer('-').next).to eq('-'.to_sym)
33
+ expect(lexer('+').next).to eq('+'.to_sym)
34
+ expect(lexer('--+-').next).to eq('--+-'.to_sym)
35
+ end
36
+
37
+ it 'reads double decimal points followed by digits as symbols' do
38
+ expect(lexer('..77').next).to eq('..77'.to_sym)
39
+ end
40
+
41
+ it "accepts =-*/+_?$!@~><&%'#`;:{} in symbols" do
42
+ all_punctuation = "=-*/+_?$!@~><&%'#`;:{}"
43
+ sym = lexer(all_punctuation).next
44
+ expect(sym).to be_kind_of(Symbol)
45
+ expect(sym.to_s).to eq(all_punctuation)
46
+ end
47
+ end
48
+
49
+ describe 'numbers' do
50
+ it 'reads integers as Fixnums' do
51
+ num = lexer("37").next
52
+ expect(num).to be_kind_of(Fixnum)
53
+ expect(num).to eq(37)
54
+ end
55
+
56
+ it 'reads floating points as Floats' do
57
+ num = lexer("37.42").next
58
+ expect(num).to be_kind_of(Float)
59
+ expect(num).to eq(37.42)
60
+ end
61
+
62
+ it 'with an odd number of leading minuses are negative' do
63
+ expect(lexer('-1').next).to eq(-1)
64
+ expect(lexer('---1').next).to eq(-1)
65
+ end
66
+
67
+ it 'with an even number of leading minuses are positive' do
68
+ expect(lexer('--1').next).to eq(1)
69
+ expect(lexer('----1').next).to eq(1)
70
+ end
71
+
72
+ it 'with leading + does not change sign' do
73
+ expect(lexer('+-1').next).to eq(-1)
74
+ expect(lexer('-+--1').next).to eq(-1)
75
+ expect(lexer('-+-+1').next).to eq(1)
76
+ expect(lexer('+-+-+-+-+1').next).to eq(1)
77
+ end
78
+
79
+ it 'allows leading decimal points' do
80
+ expect(lexer('.9').next).to eq(0.9)
81
+ expect(lexer('-.9').next).to eq(-0.9)
82
+ end
83
+
84
+ it 'treats a trailing decimal followed by EOF as a symbol' do
85
+ l = lexer('7.')
86
+ num = l.next
87
+ expect(num).to be_kind_of(Fixnum)
88
+ expect(num).to eq(7)
89
+
90
+ sym = l.next
91
+ expect(sym).to be_kind_of(Symbol)
92
+ expect(sym.to_s).to eq('.')
93
+ end
94
+
95
+ it 'treats a trailing decimal followed by non-digit as a symbol' do
96
+ l = lexer('7.a')
97
+ num = l.next
98
+ expect(num).to be_kind_of(Fixnum)
99
+ expect(num).to eq(7)
100
+
101
+ sym = l.next
102
+ expect(sym).to be_kind_of(Symbol)
103
+ expect(sym.to_s).to eq('.a')
104
+ end
105
+
106
+ it 'handles multiple decimal points like shen does' do
107
+ l = lexer('7.8.9')
108
+ expect(l.next).to eq(7.8)
109
+ expect(l.next).to eq(0.9)
110
+ end
111
+ end
112
+
113
+ describe 'booleans' do
114
+ it 'reads true as boolean true' do
115
+ expect(lexer('true').next).to be_kind_of(TrueClass)
116
+ end
117
+
118
+ it 'reads false as boolean false' do
119
+ expect(lexer('false').next).to be_kind_of(FalseClass)
120
+ end
121
+ end
122
+
123
+ describe 'whitespace' do
124
+ it 'is ignored between tokens' do
125
+ l = lexer(" (\n\t) ")
126
+ expect(l.next).to be_kind_of(Klam::Lexer::OpenParen)
127
+ expect(l.next).to be_kind_of(Klam::Lexer::CloseParen)
128
+ expect(l.next).to be_nil
129
+ end
130
+
131
+ it 'is left intact in strings' do
132
+ expect(lexer(' "one two" ').next).to eq("one two")
133
+ end
134
+ end
135
+
136
+ it 'works with these all together' do
137
+ l = lexer('(12 quick m-*$ RAN `fast\' -.7) "oh 12 yeah!" ')
138
+ expect(l.next).to be_kind_of(Klam::Lexer::OpenParen)
139
+ expect(l.next).to eq(12)
140
+ expect(l.next).to eq(:quick)
141
+ expect(l.next).to eq('m-*$'.to_sym)
142
+ expect(l.next).to eq(:RAN)
143
+ expect(l.next).to eq("`fast'".to_sym)
144
+ expect(l.next).to eq(-0.7)
145
+ expect(l.next).to be_kind_of(Klam::Lexer::CloseParen)
146
+ expect(l.next).to eq("oh 12 yeah!")
147
+ expect(l.next).to be_nil
148
+ end
149
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klam::Primitives::Arithmetic do
4
+ include Klam::Primitives::Arithmetic
5
+
6
+ describe '+' do
7
+ it 'adds its first argument to its second' do
8
+ expect(send(:+, 1, 2)).to be(3)
9
+ end
10
+
11
+ it 'returns an integer when both arguments are integers' do
12
+ expect(send(:+, 1, 2)).to be_kind_of(Fixnum)
13
+ end
14
+
15
+ it 'returns a real when either arguments are reals' do
16
+ real_first = send(:+, 1.1, 2)
17
+ real_second = send(:+, 1, 2.1)
18
+ real_both = send(:+, 1.1, 2.1)
19
+ types = [real_first, real_second, real_both].map(&:class)
20
+
21
+ expect(types.uniq).to eq([Float])
22
+ end
23
+ end
24
+
25
+ describe '-' do
26
+ it 'subtracts its second argument from its second' do
27
+ expect(send(:-, 1, 2)).to be(-1)
28
+ end
29
+
30
+ it 'returns an integer when both arguments are integers' do
31
+ expect(send(:-, 1, 2)).to be_kind_of(Fixnum)
32
+ end
33
+
34
+ it 'returns a real when either arguments are reals' do
35
+ real_first = send(:-, 1.1, 2)
36
+ real_second = send(:-, 1, 2.1)
37
+ real_both = send(:-, 1.1, 2.1)
38
+ types = [real_first, real_second, real_both].map(&:class)
39
+
40
+ expect(types.uniq).to eq([Float])
41
+ end
42
+ end
43
+
44
+ describe '*' do
45
+ it 'multiplies its first argument by its second' do
46
+ expect(send(:*, 2, 3)).to be(6)
47
+ end
48
+
49
+ it 'returns an integer when both arguments are integers' do
50
+ expect(send(:*, 1, 2)).to be_kind_of(Fixnum)
51
+ end
52
+
53
+ it 'returns a real when either arguments are reals' do
54
+ real_first = send(:*, 1.1, 2)
55
+ real_second = send(:*, 1, 2.1)
56
+ real_both = send(:*, 1.1, 2.1)
57
+ types = [real_first, real_second, real_both].map(&:class)
58
+
59
+ expect(types.uniq).to eq([Float])
60
+ end
61
+ end
62
+
63
+ describe '/' do
64
+ it 'divides its first argument by its second' do
65
+ expect(send(:/, 6, 3)).to be(2)
66
+ end
67
+
68
+ it 'returns an integer when both arguments are integers and there is no remainder' do
69
+ expect(send(:/, 6, 2)).to be_kind_of(Fixnum)
70
+ end
71
+
72
+ it 'returns a real otherwise' do
73
+ real_first = send(:/, 1.1, 2)
74
+ real_second = send(:/, 1, 2.1)
75
+ real_both = send(:/, 1.1, 2.1)
76
+ having_remainder = send(:/, 3, 2)
77
+ types = [real_first, real_second, real_both, having_remainder]
78
+ .map(&:class)
79
+
80
+ expect(types.uniq).to eq([Float])
81
+ end
82
+ end
83
+
84
+ describe '<' do
85
+ it 'returns true is the first argument is less than the second' do
86
+ expect(send(:<, 1, 2)).to be(true)
87
+ end
88
+
89
+ it 'returns false if the first argument is equal to the second' do
90
+ expect(send(:<, 2, 2)).to be(false)
91
+ end
92
+
93
+ it 'returns false if the first argument is greater than the second' do
94
+ expect(send(:<, 3, 2)).to be(false)
95
+ end
96
+ end
97
+
98
+ describe '>' do
99
+ it 'returns false is the first argument is less than the second' do
100
+ expect(send(:>, 1, 2)).to be(false)
101
+ end
102
+
103
+ it 'returns false if the first argument is equal to the second' do
104
+ expect(send(:>, 2, 2)).to be(false)
105
+ end
106
+
107
+ it 'returns true if the first argument is greater than the second' do
108
+ expect(send(:>, 3, 2)).to be(true)
109
+ end
110
+ end
111
+
112
+ describe '<=' do
113
+ it 'returns true is the first argument is less than the second' do
114
+ expect(send(:<=, 1, 2)).to be(true)
115
+ end
116
+
117
+ it 'returns true if the first argument is equal to the second' do
118
+ expect(send(:<=, 2, 2)).to be(true)
119
+ end
120
+
121
+ it 'returns false if the first argument is greater than the second' do
122
+ expect(send(:<=, 3, 2)).to be(false)
123
+ end
124
+ end
125
+
126
+ describe '>=' do
127
+ it 'returns false is the first argument is less than the second' do
128
+ expect(send(:>=, 1, 2)).to be(false)
129
+ end
130
+
131
+ it 'returns true if the first argument is equal to the second' do
132
+ expect(send(:>=, 2, 2)).to be(true)
133
+ end
134
+
135
+ it 'returns true if the first argument is greater than the second' do
136
+ expect(send(:>=, 3, 2)).to be(true)
137
+ end
138
+ end
139
+
140
+ describe 'number?' do
141
+ it 'returns true for integers' do
142
+ expect(number?(1)).to be(true)
143
+ end
144
+
145
+ it 'returns true for reals' do
146
+ expect(number?(1.1)).to be(true)
147
+ end
148
+
149
+ it 'returns false otherwise' do
150
+ expect(number?('hi')).to be(false)
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klam::Primitives::BooleanOperations do
4
+ include Klam::Primitives::BooleanOperations
5
+
6
+ describe '(if TestVal TrueVal FalseVal)' do
7
+ it 'returns TrueVal if TestVal is true' do
8
+ expect(send(:if, true, :foo, :bar)).to eq(:foo)
9
+ end
10
+
11
+ it 'returns FalseVal if TestVal is false' do
12
+ expect(send(:if, false, :foo, :bar)).to eq(:bar)
13
+ end
14
+ end
15
+
16
+ describe '(and A B)' do
17
+ it 'returns true when both A and B are true' do
18
+ expect(send(:and, true, true)).to be(true)
19
+ end
20
+
21
+ it 'returns false otherwise' do
22
+ expect(send(:and, false, true)).to be(false)
23
+ expect(send(:and, true, false)).to be(false)
24
+ expect(send(:and, false, false)).to be(false)
25
+ end
26
+ end
27
+
28
+ describe '(or A B)' do
29
+ it 'returns false when both A and B are false' do
30
+ expect(send(:or, false, false)).to be(false)
31
+ end
32
+
33
+ it 'returns true otherwise' do
34
+ expect(send(:or, false, true)).to be(true)
35
+ expect(send(:or, true, false)).to be(true)
36
+ expect(send(:or, true, true)).to be(true)
37
+ end
38
+ end
39
+ end