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,23 @@
1
+ module Klam
2
+ module Primitives
3
+ module Lists
4
+ EMPTY_LIST = nil
5
+
6
+ def cons(head, tail)
7
+ ::Klam::Cons.new(head, tail)
8
+ end
9
+
10
+ def hd(l)
11
+ l.hd
12
+ end
13
+
14
+ def tl(l)
15
+ l.tl
16
+ end
17
+
18
+ def cons?(l)
19
+ l.instance_of?(::Klam::Cons)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ module Klam
2
+ module Primitives
3
+ module Streams
4
+ def read_byte(stream)
5
+ if stream.eof?
6
+ -1
7
+ else
8
+ stream.readbyte
9
+ end
10
+ end
11
+ alias_method :"read-byte", :read_byte
12
+ remove_method :read_byte
13
+
14
+ def write_byte(byte, stream)
15
+ stream.putc byte
16
+ byte
17
+ end
18
+ alias_method :"write-byte", :write_byte
19
+ remove_method :write_byte
20
+
21
+ def open(name, direction)
22
+ ::File.open(::File.expand_path(name, value(:'*home-directory*')),
23
+ direction == :out ? 'w' : 'r')
24
+ end
25
+
26
+ def close(stream)
27
+ stream.close
28
+ :NIL
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,58 @@
1
+ module Klam
2
+ module Primitives
3
+ module Strings
4
+ def pos(str, n)
5
+ if n < 0 || n >= str.length
6
+ ::Kernel.raise ::Klam::Error, "index out of bounds: #{n}"
7
+ end
8
+ str[n]
9
+ end
10
+
11
+ def tlstr(str)
12
+ if str.empty?
13
+ ::Kernel.raise ::Klam::Error, 'attempted to take tail of empty string'
14
+ end
15
+ str[1..-1]
16
+ end
17
+
18
+ def cn(s1, s2)
19
+ s1 + s2
20
+ end
21
+
22
+ def str(x)
23
+ case x
24
+ when String
25
+ '"' + x + '"'
26
+ when Symbol
27
+ x.to_s
28
+ when Numeric
29
+ x.to_s
30
+ when TrueClass, FalseClass
31
+ x.to_s
32
+ when Proc
33
+ x.to_s
34
+ when IO
35
+ x.to_s
36
+ else
37
+ ::Kernel.raise ::Klam::Error, "str applied to non-atomic type: #{x.class}"
38
+ end
39
+ end
40
+
41
+ def string?(x)
42
+ x.kind_of?(String)
43
+ end
44
+
45
+ def n_to_string(n)
46
+ '' << n
47
+ end
48
+ alias_method :"n->string", :n_to_string
49
+ remove_method :n_to_string
50
+
51
+ def string_to_n(str)
52
+ str.ord
53
+ end
54
+ alias_method :"string->n", :string_to_n
55
+ remove_method :string_to_n
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,16 @@
1
+ module Klam
2
+ module Primitives
3
+ module Symbols
4
+ def intern(str)
5
+ case str
6
+ when 'true'
7
+ true
8
+ when 'false'
9
+ false
10
+ else
11
+ str.intern
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ module Klam
2
+ module Primitives
3
+ module Time
4
+ def get_time(type)
5
+ case type
6
+ when :real, :run
7
+ ::Time.now.to_f
8
+ when :unix
9
+ ::Time.now.to_i
10
+ else
11
+ ::Kernel.raise ::Klam::Error, "invalid time parameter: #{type}"
12
+ end
13
+
14
+ end
15
+ alias_method :"get-time", :get_time
16
+ remove_method :get_time
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module Klam
2
+ module Primitives
3
+ module Vectors
4
+ def absvector(n)
5
+ ::Array.new(n)
6
+ end
7
+
8
+ def absvec_store(vec, n, val)
9
+ vec[n] = val
10
+ vec
11
+ end
12
+ alias_method :"address->", :absvec_store
13
+ remove_method :absvec_store
14
+
15
+ def absvec_read(vec, n)
16
+ vec[n]
17
+ end
18
+ alias_method :"<-address", :absvec_read
19
+ remove_method :absvec_read
20
+
21
+ def absvector?(v)
22
+ v.instance_of?(::Array)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ module Klam
2
+ class Reader
3
+ include Klam::Converters::List
4
+
5
+ def initialize(stream)
6
+ @lexer = Klam::Lexer.new(stream)
7
+ end
8
+
9
+ def next
10
+ token = @lexer.next
11
+ unless token.nil?
12
+ if token.kind_of? Klam::Lexer::OpenParen
13
+ read_list
14
+ else
15
+ token
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def read_list
23
+ items = []
24
+ stack = [items]
25
+
26
+ until stack.empty? do
27
+ token = @lexer.next
28
+ raise Klam::SyntaxError, 'Unterminated list' if token.nil?
29
+ case token
30
+ when Klam::Lexer::OpenParen
31
+ items = []
32
+ stack.push items
33
+ when Klam::Lexer::CloseParen
34
+ array = stack.pop
35
+ unless stack.empty?
36
+ items = stack.last
37
+ items << array
38
+ end
39
+ else
40
+ items << token
41
+ end
42
+ end
43
+ arrayToList(array)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,38 @@
1
+ module Klam
2
+ module Template
3
+ def render_string(template, *args)
4
+ args = join_array_arguments(args)
5
+ segments = segment_string(template)
6
+ segments.map do |segment|
7
+ if segment =~ /^\$(\d+)$/
8
+ args[$1.to_i - 1]
9
+ else
10
+ segment
11
+ end
12
+ end.join
13
+ end
14
+
15
+ private
16
+
17
+ def join_array_arguments(args)
18
+ args.map do |arg|
19
+ if arg.kind_of?(Array)
20
+ arg.join(',')
21
+ else
22
+ arg
23
+ end
24
+ end
25
+ end
26
+ def segment_string(str)
27
+ segments = []
28
+ pre, placeholder, str = str.partition(/\$\d+/)
29
+ until placeholder.empty? && str.empty?
30
+ segments << pre
31
+ segments << placeholder
32
+ pre, placeholder, str = str.partition(/\$\d+/)
33
+ end
34
+ segments << pre
35
+ segments
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ module Klam
2
+ class Variable
3
+ attr_reader :name
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+
9
+ def to_s
10
+ name
11
+ end
12
+
13
+ def ==(other)
14
+ other.kind_of?(Variable) && name == other.name
15
+ end
16
+
17
+ def hash
18
+ name.hash
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ module Klam
2
+ class VariableGenerator
3
+ def initialize
4
+ @index = 0
5
+ end
6
+
7
+ def next
8
+ @index += 1
9
+ Klam::Variable.new('__KLAM_%03d' % @index)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Klam
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Application', :type => :functional do
4
+ describe 'of functions' do
5
+ describe 'when all arguments are provided' do
6
+ it 'returns the result of applying the function to its arguments' do
7
+ eval_kl('(defun add (A B) (+ A B))')
8
+ expect_kl('(add 2 3)').to eq(5)
9
+ end
10
+ end
11
+
12
+ describe 'when not all arguments are provided' do
13
+ before(:each) do
14
+ eval_kl('(defun add3 (A B C) (+ (+ A B) C))')
15
+ end
16
+
17
+ it 'returns a function' do
18
+ expect_kl('(add3 1)').to be_kind_of(Proc)
19
+ end
20
+
21
+ it 'evaluates once all of the arguments are provided' do
22
+ expect_kl('((add3 1) 2 3)').to eq(6)
23
+ end
24
+
25
+ it 'allows further partial application' do
26
+ expect_kl('(((add3 1) 2) 3)').to eq(6)
27
+ end
28
+ end
29
+
30
+ describe 'uncurrying' do
31
+ before(:each) do
32
+ eval_kl('(defun add3 (A) (lambda B (lambda C (+ (+ A B) C))))')
33
+ end
34
+
35
+ it 'provides the equivalent behavior to defining a polyadic function' do
36
+ expect_kl('(add3 1 2 3)').to eq(6)
37
+ expect_kl('((add3 1 2) 3)').to eq(6)
38
+ expect_kl('((add3 1) 2 3)').to eq(6)
39
+ end
40
+ end
41
+
42
+ describe 'when the function takes no arguments' do
43
+ it 'returns the result of evaluating the function\'s body' do
44
+ eval_kl('(defun thirty-seven () 37)')
45
+ expect_kl('(thirty-seven)').to eq(37)
46
+ end
47
+ end
48
+ end
49
+
50
+ describe 'of abstractions' do
51
+ describe 'when applied directly' do
52
+ it 'returns the result of applying the abstraction to its argument' do
53
+ expect_kl('((lambda X (+ X 3)) 2)').to eq(5)
54
+ end
55
+
56
+ it 'uncurries additional arguments' do
57
+ expect_kl('((lambda A (lambda B (lambda C (+ (+ A B) C)))) 1 2 3)')
58
+ .to eq(6)
59
+ end
60
+ end
61
+
62
+ describe 'when returned from another function' do
63
+ it 'returns the result of applying the returned abstraction to its arg' do
64
+ eval_kl('(defun make-adder (X) (lambda Y (+ X Y)))')
65
+ expect_kl('((make-adder 3) 2)').to eq(5)
66
+ end
67
+ end
68
+ end
69
+
70
+ describe "of variables" do
71
+ describe "when bound to functions" do
72
+ describe 'when applied directly' do
73
+ it 'returns the result of applying the abstraction to its argument' do
74
+ expect_kl('(let F (lambda X (+ X 3)) (F 2))').to eq(5)
75
+ end
76
+
77
+ it 'uncurries additional arguments' do
78
+ expect_kl('(let F (lambda A (lambda B (lambda C (+ (+ A B) C)))) (F 1 2 3))')
79
+ .to eq(6)
80
+ end
81
+ end
82
+ end
83
+
84
+ describe "when bound to symbols" do
85
+ it 'invokes the function bound to the symobl in the global environment' do
86
+ expect_kl('(let F + (F 1 2))').to eq(3)
87
+ end
88
+
89
+ it 'supports partial evaluation' do
90
+ expect_kl('(let F + ((F 1) 2))').to eq(3)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Atoms', :type => :functional do
4
+ describe 'numbers' do
5
+ it 'integers evaluate to themselves' do
6
+ expect_kl('37').to eq(read_kl('37'))
7
+ end
8
+
9
+ it 'reals evaluate to themselves' do
10
+ expect_kl('37.42').to eq(read_kl('37.42'))
11
+ end
12
+ end
13
+
14
+ describe 'symbols' do
15
+ it 'evaluate to themselves' do
16
+ expect_kl('sym').to eq(read_kl('sym'))
17
+ end
18
+
19
+ it 'supports the full range of initial symbol characters' do
20
+ # See http://www.shenlanguage.org/learn-shen/shendoc.htm#The%20Syntax%20of%20Symbols
21
+ all_chars = 'abcdefghijklmnopqrstuvwxyz'
22
+ all_chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
23
+ all_chars += "=-*/+_?$!@~.><&%'#`;:{}"
24
+
25
+ all_chars.each_char do |c|
26
+ expect_kl(c).to eq(read_kl(c))
27
+ end
28
+ end
29
+ end
30
+
31
+ describe 'strings' do
32
+ it 'evaluate to themselves' do
33
+ expect_kl('"a string"').to eq(read_kl('"a string"'))
34
+ end
35
+
36
+ it 'supports embedded single quotes' do
37
+ expect_kl('"\'quote\'"').to eq(read_kl('"\'quote\'"'))
38
+ end
39
+ end
40
+
41
+ describe 'booleans' do
42
+ it 'evaluates true to itself' do
43
+ expect_kl('true').to eq(read_kl('true'))
44
+ end
45
+
46
+ it 'evaluates false to itself' do
47
+ expect_kl('false').to eq(read_kl('false'))
48
+ end
49
+ end
50
+
51
+ describe 'empty list' do
52
+ it 'evaluates to itself' do
53
+ expect_kl('()').to be(Klam::Primitives::Lists::EMPTY_LIST)
54
+ end
55
+ end
56
+ end