klam 0.0.1

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