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
data/lib/klam/cons.rb ADDED
@@ -0,0 +1,18 @@
1
+ module Klam
2
+ class Cons
3
+ attr_reader :hd, :tl
4
+
5
+ def initialize(hd, tl)
6
+ @hd = hd
7
+ @tl = tl
8
+ end
9
+
10
+ def ==(other)
11
+ other.instance_of?(Cons) && other.hd == @hd && other.tl == @tl
12
+ end
13
+
14
+ def hash
15
+ [@hd, @tl].hash
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,4 @@
1
+ this_dir = File.dirname(__FILE__)
2
+ Dir[File.join(this_dir, 'converters', '*.rb')].each do |file|
3
+ require file
4
+ end
Binary file
@@ -0,0 +1,29 @@
1
+ module Klam
2
+ module Converters
3
+ # Utility methods to convert between Ruby Arrays and Kl Lists
4
+ module List
5
+ include Klam::Primitives::Lists
6
+
7
+ def arrayToList(array)
8
+ list = EMPTY_LIST
9
+ (array.length - 1).downto(0) do |index|
10
+ item = array[index]
11
+ item = arrayToList(item) if item.kind_of?(Array)
12
+ list = cons(item, list)
13
+ end
14
+ list
15
+ end
16
+
17
+ def listToArray(list)
18
+ array = []
19
+ while list != EMPTY_LIST
20
+ item = hd(list)
21
+ item = listToArray(item) if cons?(item) || item == EMPTY_LIST
22
+ array << item
23
+ list = tl(list)
24
+ end
25
+ array
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,61 @@
1
+ module Klam
2
+ class Environment < BasicObject
3
+ include ::Klam::Primitives::BooleanOperations
4
+ include ::Klam::Primitives::Symbols
5
+ include ::Klam::Primitives::Strings
6
+ include ::Klam::Primitives::Assignments
7
+ include ::Klam::Primitives::ErrorHandling
8
+ include ::Klam::Primitives::Lists
9
+ include ::Klam::Primitives::GenericFunctions
10
+ include ::Klam::Primitives::Vectors
11
+ include ::Klam::Primitives::Streams
12
+ include ::Klam::Primitives::Time
13
+ include ::Klam::Primitives::Arithmetic
14
+
15
+ def initialize
16
+ # The global assignments namespace. Errors are thrown here in the
17
+ # missing element handler rather than in the value primitive in order
18
+ # to facilitate inlining later.
19
+ @assignments = ::Hash.new do |_, name|
20
+ ::Kernel.raise ::Klam::Error, "The variable #{name} is unbound."
21
+ end
22
+
23
+ @arities = ::Hash.new { |h, k| h[k] = __arity(k) }
24
+ @curried_methods = ::Hash.new { |h, k| h[k] = __method(k).to_proc.curry }
25
+ @loop_cache = {}
26
+
27
+ # Grab a handle to this object's eigenclass for use later when the
28
+ # compiled code needs to reference it. It is used, e.g., when renaming
29
+ # methods.
30
+ @eigenclass = class << self; self; end
31
+
32
+ # The open primitive depends on having *home-directory* assigned.
33
+ set(:"*home-directory*", ::Dir.pwd)
34
+ end
35
+
36
+ def __apply(rator, *rands)
37
+ if rator.kind_of?(::Symbol)
38
+ @curried_methods[rator].call(*rands)
39
+ else
40
+ rator.call(*rands)
41
+ end
42
+ end
43
+
44
+ def __method(sym)
45
+ @eigenclass.instance_method(sym).bind(self)
46
+ end
47
+
48
+ def __arity(sym)
49
+ @eigenclass.instance_method(sym).arity
50
+ rescue ::NameError
51
+ -1
52
+ end
53
+
54
+ class << self
55
+ def rename_method(old_name, new_name)
56
+ alias_method(new_name, old_name)
57
+ remove_method(old_name)
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/klam/error.rb ADDED
@@ -0,0 +1,4 @@
1
+ module Klam
2
+ class Error < StandardError; end
3
+ class SyntaxError < Error; end
4
+ end
data/lib/klam/lexer.rb ADDED
@@ -0,0 +1,185 @@
1
+ require 'singleton'
2
+
3
+ module Klam
4
+ class Lexer
5
+ SYMBOL_CHARS = /[-=*\/+_?$!\@~><&%'#`;:{}a-zA-Z0-9.]/
6
+
7
+ # Syntax tokens
8
+ class OpenParen
9
+ include Singleton
10
+ end
11
+ class CloseParen
12
+ include Singleton
13
+ end
14
+
15
+ def initialize(stream)
16
+ @stream = stream
17
+ @buffer = []
18
+ end
19
+
20
+ def eof?
21
+ @buffer.empty? && @stream.eof?
22
+ end
23
+
24
+ def getc
25
+ if @buffer.empty?
26
+ @stream.getc
27
+ else
28
+ @buffer.pop
29
+ end
30
+ end
31
+
32
+ def ungetc(c)
33
+ @buffer.push(c)
34
+ end
35
+
36
+ def next
37
+ drain_whitespace
38
+ unless eof?
39
+ c = getc
40
+ case c
41
+ when '('
42
+ OpenParen.instance
43
+ when ')'
44
+ CloseParen.instance
45
+ when '"'
46
+ consume_string
47
+ when SYMBOL_CHARS
48
+ ungetc(c)
49
+ consume_number_or_symbol
50
+ else
51
+ raise Klam::SyntaxError, "illegal character: #{c}"
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+ def drain_whitespace
58
+ until eof?
59
+ c = getc
60
+ if c =~ /\S/
61
+ ungetc(c)
62
+ break
63
+ end
64
+ end
65
+ end
66
+
67
+ def consume_string
68
+ chars = []
69
+ loop do
70
+ raise Klam::SyntaxError, "unterminated string" if eof?
71
+ c = getc
72
+ break if c == '"'
73
+ chars << c
74
+ end
75
+ chars.join
76
+ end
77
+
78
+ def consume_number
79
+ # Shen allows multiple leading plusses and minuses. The plusses
80
+ # are ignored and an even number of minuses cancel each other.
81
+ # Thus '------+-7' is read as 7.
82
+ #
83
+ # The Shen reader parses "7." as the integer 7 and the symbol '.'
84
+ decimal_seen = false
85
+ negative = false
86
+ past_sign = false
87
+ chars = []
88
+ loop do
89
+ break if eof?
90
+ c = getc
91
+ if c =~ /\d/
92
+ past_sign = true
93
+ chars << c
94
+ elsif c == '.' && !decimal_seen
95
+ past_sign = true
96
+ decimal_seen = true
97
+ chars << c
98
+ elsif c == '+' && !past_sign
99
+ # ignore
100
+ elsif c == '-' && !past_sign
101
+ negative = !negative
102
+ else
103
+ ungetc c
104
+ break
105
+ end
106
+ end
107
+ chars.unshift('-') if negative
108
+ if chars.last == '.'
109
+ # A trailing decimal point is treated as part of the next
110
+ # token. Forget we saw it.
111
+ ungetc(chars.pop)
112
+ decimal_seen = false
113
+ end
114
+ str = chars.join
115
+ decimal_seen ? str.to_f : str.to_i
116
+ end
117
+
118
+ def consume_symbol
119
+ chars = []
120
+ loop do
121
+ break if eof?
122
+ c = getc
123
+ unless c =~ SYMBOL_CHARS
124
+ ungetc c
125
+ break
126
+ end
127
+ chars << c
128
+ end
129
+ str = chars.join
130
+
131
+ case str
132
+ when 'true'
133
+ true
134
+ when 'false'
135
+ false
136
+ else
137
+ str.to_sym
138
+ end
139
+ end
140
+
141
+ def consume_number_or_symbol
142
+ # First drain optional leading signs
143
+ # Then drain optional decimal point
144
+ # If there is another character and it is a digit, then it
145
+ # is a number. Otherwise it is a symbol.
146
+ chars = []
147
+ loop do
148
+ break if eof?
149
+ c = getc
150
+ unless c =~ /[-+]/
151
+ ungetc c
152
+ break
153
+ end
154
+ chars << c
155
+ end
156
+ if eof?
157
+ chars.reverse.each {|x| ungetc x}
158
+ return consume_symbol
159
+ end
160
+
161
+ c = getc
162
+ chars << c
163
+ if c == '.'
164
+ if eof?
165
+ chars.reverse.each {|x| ungetc x}
166
+ return consume_symbol
167
+ end
168
+ c = getc
169
+ chars << c
170
+ chars.reverse.each {|x| ungetc x}
171
+ if c =~ /\d/
172
+ return consume_number
173
+ else
174
+ return consume_symbol
175
+ end
176
+ elsif c =~ /\d/
177
+ chars.reverse.each {|x| ungetc x}
178
+ return consume_number
179
+ else
180
+ chars.reverse.each {|x| ungetc x}
181
+ return consume_symbol
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,4 @@
1
+ this_dir = File.dirname(__FILE__)
2
+ Dir[File.join(this_dir, 'primitives', '*.rb')].each do |file|
3
+ require file
4
+ end
@@ -0,0 +1,49 @@
1
+ module Klam
2
+ module Primitives
3
+ module Arithmetic
4
+ def +(a, b)
5
+ a + b
6
+ end
7
+
8
+ def -(a, b)
9
+ a - b
10
+ end
11
+
12
+ def *(a, b)
13
+ a * b
14
+ end
15
+
16
+ def /(a, b)
17
+ # Kl does not make a distinction between integers and reals. Dividing
18
+ # the integer 3 by the interger 2 must yield 1.5 rather than 1. We'd
19
+ # like to keep things in integers as much as possible, so we coerce a
20
+ # to a float only if integer division is not possible.
21
+ if a.kind_of?(Fixnum) && b.kind_of?(Fixnum) && a.remainder(b) != 0
22
+ a = a.to_f
23
+ end
24
+
25
+ a / b
26
+ end
27
+
28
+ def <(a, b)
29
+ a < b
30
+ end
31
+
32
+ def >(a, b)
33
+ a > b
34
+ end
35
+
36
+ def <=(a, b)
37
+ a <= b
38
+ end
39
+
40
+ def >=(a, b)
41
+ a >= b
42
+ end
43
+
44
+ def number?(a)
45
+ a.kind_of?(Numeric)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,13 @@
1
+ module Klam
2
+ module Primitives
3
+ module Assignments
4
+ def set(name, value)
5
+ @assignments[name] = value
6
+ end
7
+
8
+ def value(name)
9
+ @assignments[name]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module Klam
2
+ module Primitives
3
+ # All of the boolean operations are special forms, but if, and, and or
4
+ # are also available as normal functions to facilitate partial application.
5
+ # When partially applied, they are no longer short circuiting.
6
+ module BooleanOperations
7
+ def _if(a, b, c)
8
+ a ? b : c
9
+ end
10
+ alias_method :'if', :_if
11
+ remove_method :_if
12
+
13
+ def and(a, b)
14
+ a && b
15
+ end
16
+
17
+ def or(a, b)
18
+ a || b
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Klam
2
+ module Primitives
3
+ module ErrorHandling
4
+ def simple_error(msg)
5
+ ::Kernel.raise ::Klam::Error, msg
6
+ end
7
+ alias_method :"simple-error", :simple_error
8
+ remove_method :simple_error
9
+
10
+ # trap-error is a special form and implemented in the compiler
11
+
12
+ def error_to_string(err)
13
+ err.message
14
+ end
15
+ alias_method :"error-to-string", :error_to_string
16
+ remove_method :error_to_string
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Klam
2
+ module Primitives
3
+ module GenericFunctions
4
+ def equal(a, b)
5
+ a == b
6
+ end
7
+ alias_method :"=", :equal
8
+ remove_method :equal
9
+
10
+ def eval_kl(form)
11
+ compiler = Klam::Compiler.new(self)
12
+ code = compiler.compile(form)
13
+ instance_eval code
14
+ end
15
+ alias_method :"eval-kl", :eval_kl
16
+ remove_method :eval_kl
17
+ end
18
+ end
19
+ end