apricot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +1 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +26 -0
  7. data/README.md +90 -0
  8. data/Rakefile +9 -0
  9. data/apricot.gemspec +22 -0
  10. data/bin/apricot +58 -0
  11. data/examples/bot.apr +23 -0
  12. data/examples/cinch-bot.apr +12 -0
  13. data/examples/hanoi.apr +10 -0
  14. data/examples/hello.apr +1 -0
  15. data/examples/plot.apr +28 -0
  16. data/examples/quine.apr +1 -0
  17. data/kernel/core.apr +928 -0
  18. data/lib/apricot/ast/identifier.rb +111 -0
  19. data/lib/apricot/ast/list.rb +99 -0
  20. data/lib/apricot/ast/literals.rb +240 -0
  21. data/lib/apricot/ast/node.rb +45 -0
  22. data/lib/apricot/ast/scopes.rb +147 -0
  23. data/lib/apricot/ast/toplevel.rb +66 -0
  24. data/lib/apricot/ast/variables.rb +64 -0
  25. data/lib/apricot/ast.rb +3 -0
  26. data/lib/apricot/compiler.rb +55 -0
  27. data/lib/apricot/cons.rb +27 -0
  28. data/lib/apricot/errors.rb +38 -0
  29. data/lib/apricot/generator.rb +15 -0
  30. data/lib/apricot/identifier.rb +91 -0
  31. data/lib/apricot/list.rb +96 -0
  32. data/lib/apricot/macroexpand.rb +47 -0
  33. data/lib/apricot/misc.rb +11 -0
  34. data/lib/apricot/namespace.rb +59 -0
  35. data/lib/apricot/parser.rb +541 -0
  36. data/lib/apricot/printers.rb +12 -0
  37. data/lib/apricot/repl.rb +254 -0
  38. data/lib/apricot/ruby_ext.rb +254 -0
  39. data/lib/apricot/seq.rb +44 -0
  40. data/lib/apricot/special_forms.rb +735 -0
  41. data/lib/apricot/stages.rb +60 -0
  42. data/lib/apricot/version.rb +3 -0
  43. data/lib/apricot.rb +30 -0
  44. data/spec/compiler_spec.rb +499 -0
  45. data/spec/identifier_spec.rb +58 -0
  46. data/spec/list_spec.rb +96 -0
  47. data/spec/parser_spec.rb +312 -0
  48. data/spec/spec_helper.rb +10 -0
  49. metadata +188 -0
@@ -0,0 +1,64 @@
1
+ module Apricot
2
+ module AST
3
+ class LocalReference
4
+ attr_reader :slot, :depth
5
+
6
+ def initialize(slot, depth = 0)
7
+ @slot = slot
8
+ @depth = depth
9
+ end
10
+
11
+ def bytecode(g)
12
+ if @depth == 0
13
+ g.push_local @slot
14
+ else
15
+ g.push_local_depth @depth, @slot
16
+ end
17
+ end
18
+ end
19
+
20
+ class NamespaceReference
21
+ def initialize(name, ns = nil)
22
+ @name = name
23
+ @ns = ns || Apricot.current_namespace
24
+ end
25
+
26
+ def bytecode(g)
27
+ if @ns.is_a?(Namespace) && !@ns.vars.include?(@name)
28
+ g.compile_error "Unable to resolve name #{@name} in namespace #{@ns}"
29
+ end
30
+
31
+ ns_id = Apricot::Identifier.intern(@ns.name)
32
+ g.push_const ns_id.const_names.first
33
+ ns_id.const_names.drop(1).each {|n| g.find_const(n) }
34
+
35
+ g.push_literal @name
36
+
37
+ if @ns.is_a? Namespace
38
+ g.send :get_var, 1
39
+ else # @ns is a regular Ruby module
40
+ g.send :method, 1
41
+ end
42
+ end
43
+
44
+ def meta
45
+ @ns.is_a?(Namespace) && @ns.vars[@name] && @ns.vars[@name].apricot_meta
46
+ end
47
+
48
+ def fn?
49
+ @ns.is_a?(Namespace) && @ns.fns.include?(@name)
50
+ end
51
+
52
+ def method?
53
+ !@ns.is_a?(Namespace) && @ns.respond_to?(@name)
54
+ end
55
+ end
56
+
57
+ # For the 'self' identifier. Just like Ruby's 'self'.
58
+ class SelfReference
59
+ def bytecode(g)
60
+ g.push_self
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ %w[node literals identifier list scopes variables toplevel].each do |r|
2
+ require "apricot/ast/#{r}"
3
+ end
@@ -0,0 +1,55 @@
1
+ module Apricot
2
+ class Compiler < Rubinius::Compiler
3
+ def self.compile(file, output = nil, debug = false)
4
+ compiler = new :apricot_file, :compiled_file
5
+
6
+ compiler.parser.input file
7
+ compiler.packager.print(BytecodePrinter) if debug
8
+ compiler.writer.name = output || Rubinius::Compiler.compiled_name(file)
9
+
10
+ prepare_compiled_code compiler.run
11
+ end
12
+
13
+ def self.compile_string(code, file = nil, line = 1, debug = false)
14
+ compiler = new :apricot_string, :compiled_method
15
+
16
+ compiler.parser.input(code, file || "(none)", line)
17
+ compiler.packager.print(BytecodePrinter) if debug
18
+
19
+ prepare_compiled_code compiler.run
20
+ end
21
+
22
+ def self.compile_node(node, file = "(none)", line = 1, debug = false)
23
+ compiler = new :apricot_bytecode, :compiled_method
24
+
25
+ compiler.generator.input AST::TopLevel.new([node], file, line, false)
26
+ compiler.packager.print(BytecodePrinter) if debug
27
+
28
+ prepare_compiled_code compiler.run
29
+ end
30
+
31
+ def self.compile_form(node, file = "(none)", line = 1, debug = false)
32
+ compile_node(AST::Node.from_value(node, line), file, line, debug)
33
+ end
34
+
35
+ def self.prepare_compiled_code(cc)
36
+ cc.scope = Rubinius::ConstantScope.new(Object)
37
+ cc
38
+ end
39
+
40
+ def self.eval(code, file = "(eval)", line = 1, debug = false)
41
+ cc = compile_string(code, file, line, debug)
42
+ Rubinius.run_script cc
43
+ end
44
+
45
+ def self.eval_form(form, file = "(eval)", line = 1, debug = false)
46
+ cc = compile_form(form, file, line, debug)
47
+ Rubinius.run_script cc
48
+ end
49
+
50
+ def self.eval_node(node, file = "(eval)", line = 1, debug = false)
51
+ cc = compile_node(node, file, line, debug)
52
+ Rubinius.run_script cc
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,27 @@
1
+ module Apricot
2
+ class Cons
3
+ include Seq
4
+
5
+ def initialize(head, tail)
6
+ @head = head
7
+ @tail = tail.to_seq
8
+ end
9
+
10
+ def first
11
+ @head
12
+ end
13
+
14
+ def next
15
+ if @tail
16
+ @tail
17
+ else
18
+ nil
19
+ end
20
+ end
21
+
22
+ def each
23
+ yield first
24
+ @tail.each {|x| yield x }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,38 @@
1
+ module Apricot
2
+ class SyntaxError < StandardError
3
+ attr_reader :filename, :line, :msg
4
+
5
+ def initialize(filename, line, msg, incomplete = false)
6
+ @filename = filename
7
+ @line = line
8
+ @msg = msg
9
+ @incomplete = incomplete
10
+ end
11
+
12
+ def incomplete?
13
+ @incomplete
14
+ end
15
+
16
+ def to_s
17
+ "#{@filename}:#{@line}: #{@msg}"
18
+ end
19
+ end
20
+
21
+ class CompileError < StandardError
22
+ attr_reader :filename, :line, :msg
23
+
24
+ def initialize(filename, line, msg)
25
+ @filename = filename
26
+ @line = line
27
+ @msg = msg
28
+ end
29
+
30
+ def to_s
31
+ "#{@filename}:#{@line}: #{@msg}"
32
+ end
33
+ end
34
+
35
+ def self.compile_error(file, line, msg)
36
+ raise CompileError.new(file, line, msg)
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ module Apricot
2
+ class Generator < Rubinius::Generator
3
+ def scopes
4
+ @scopes ||= []
5
+ end
6
+
7
+ def scope
8
+ @scopes.last
9
+ end
10
+
11
+ def compile_error(msg)
12
+ raise CompileError.new(file, line, msg)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,91 @@
1
+ module Apricot
2
+ class Identifier
3
+ attr_reader :name, :ns, :unqualified_name
4
+
5
+ @table = {}
6
+
7
+ def self.intern(name)
8
+ name = name.to_sym
9
+ @table[name] ||= new(name)
10
+ end
11
+
12
+ private_class_method :new
13
+
14
+ def initialize(name)
15
+ @name = name
16
+
17
+ if @name =~ /\A(?:[A-Z]\w*::)*[A-Z]\w*\z/
18
+ @constant = true
19
+ @const_names = @name.to_s.split('::').map(&:to_sym)
20
+ elsif @name =~ /\A(.+?)\/(.+)\z/
21
+ @qualified = true
22
+ ns_id = Identifier.intern($1)
23
+ raise 'namespace in identifier must be a constant' unless ns_id.constant?
24
+
25
+ @ns = ns_id.const_names.reduce(Object) do |mod, name|
26
+ mod.const_get(name)
27
+ end
28
+
29
+ @unqualified_name = $2.to_sym
30
+ else
31
+ @ns = Apricot.current_namespace
32
+ @unqualified_name = name
33
+ end
34
+ end
35
+
36
+ def qualified?
37
+ @qualified
38
+ end
39
+
40
+ def unqualified_name
41
+ @unqualified_name
42
+ end
43
+
44
+ def constant?
45
+ @constant
46
+ end
47
+
48
+ def const_names
49
+ raise "#{@name} is not a constant" unless constant?
50
+ @const_names
51
+ end
52
+
53
+ # Copying Identifiers is not allowed.
54
+ def initialize_copy(other)
55
+ raise TypeError, "copy of #{self.class} is not allowed"
56
+ end
57
+
58
+ private :initialize_copy
59
+
60
+ alias_method :==, :equal?
61
+ alias_method :eql?, :equal?
62
+
63
+ def hash
64
+ @name.hash
65
+ end
66
+
67
+ def inspect
68
+ case @name
69
+ when :true, :false, :nil, /\A(?:\+|-)?\d/
70
+ # Use arbitrary identifier syntax for identifiers that would otherwise
71
+ # be parsed as keywords or numbers
72
+ str = @name.to_s.gsub(/(\\.)|\|/) { $1 || '\|' }
73
+ "#|#{str}|"
74
+ when /\A#{Apricot::Parser::IDENTIFIER}+\z/
75
+ @name.to_s
76
+ else
77
+ str = @name.to_s.inspect[1..-2]
78
+ str.gsub!(/(\\.)|\|/) { $1 || '\|' }
79
+ "#|#{str}|"
80
+ end
81
+ end
82
+
83
+ def to_s
84
+ @name.to_s
85
+ end
86
+
87
+ def to_sym
88
+ @name
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,96 @@
1
+ module Apricot
2
+ # A linked list implementation representing (a b c) syntax in Apricot
3
+ class List
4
+ include Seq
5
+
6
+ def self.[](*args)
7
+ list = EmptyList
8
+ args.reverse_each do |arg|
9
+ list = list.cons(arg)
10
+ end
11
+ list
12
+ end
13
+
14
+ attr_reader :head, :tail
15
+
16
+ def initialize(head, tail)
17
+ @head = head
18
+ @tail = tail
19
+ end
20
+
21
+ def cons(x)
22
+ List.new(x, self)
23
+ end
24
+
25
+ def each
26
+ list = self
27
+ until list.empty?
28
+ yield list.head
29
+ list = list.tail
30
+ end
31
+ end
32
+
33
+ def ==(other)
34
+ return true if self.equal? other
35
+ return false unless other.is_a? List
36
+
37
+ list = self
38
+
39
+ until list.empty?
40
+ return false if other.empty? || list.head != other.head
41
+
42
+ list = list.tail
43
+ other = other.tail
44
+ end
45
+
46
+ other.empty?
47
+ end
48
+
49
+ alias_method :eql?, :==
50
+
51
+ def hash
52
+ hashes = map {|x| x.hash }
53
+ hashes.reduce(hashes.size) {|acc,hash| acc ^ hash }
54
+ end
55
+
56
+ def empty?
57
+ !@tail
58
+ end
59
+
60
+ def initialize_copy(other)
61
+ super
62
+ @tail = other.tail.dup if other.tail && !other.tail.empty?
63
+ end
64
+
65
+ private :initialize_copy
66
+
67
+ def to_list
68
+ self
69
+ end
70
+
71
+ def first
72
+ empty? ? nil : @head
73
+ end
74
+
75
+ def next
76
+ @tail.empty? ? nil : @tail
77
+ end
78
+
79
+ def to_seq
80
+ empty? ? nil : self
81
+ end
82
+
83
+ def inspect
84
+ return '()' if empty?
85
+
86
+ str = '('
87
+ each {|x| str << x.apricot_inspect << ' ' }
88
+ str.chop!
89
+ str << ')'
90
+ end
91
+
92
+ alias_method :to_s, :inspect
93
+
94
+ EmptyList = new(nil, nil)
95
+ end
96
+ end
@@ -0,0 +1,47 @@
1
+ module Apricot
2
+ def self.macroexpand(form)
3
+ ex = macroexpand_1(form)
4
+ ex.equal?(form) ? ex : macroexpand(ex)
5
+ end
6
+
7
+ def self.macroexpand_1(form)
8
+ return form unless form.is_a? List
9
+
10
+ callee = form.first
11
+ return form unless callee.is_a?(Identifier) && !callee.constant?
12
+
13
+ name = callee.name
14
+ name_s = name.to_s
15
+ args = form.tail
16
+
17
+ # Handle the (.method receiver args*) send expression form
18
+ if name.length > 1 && name_s != '..' && name_s.start_with?('.')
19
+ raise ArgumentError, "Too few arguments to send expression, expecting (.method receiver ...)" if args.empty?
20
+
21
+ dot = Identifier.intern(:'.')
22
+ method = Identifier.intern(name_s[1..-1])
23
+ return List[dot, args.first, method, *args.tail]
24
+ end
25
+
26
+ # Handle the (Class. args*) shorthand new form
27
+ if name.length > 1 && name_s != '..' && name_s.end_with?('.')
28
+ dot = Identifier.intern(:'.')
29
+ klass = Identifier.intern(name_s[0..-2])
30
+ new = Identifier.intern(:new)
31
+ return List[dot, klass, new, *args]
32
+ end
33
+
34
+ # Handle defined macros
35
+ if callee.ns.is_a?(Namespace) && callee.ns.vars.include?(callee.unqualified_name)
36
+ potential_macro = callee.ns.get_var(callee.unqualified_name)
37
+ meta = potential_macro.apricot_meta
38
+
39
+ if meta && meta[:macro]
40
+ return potential_macro.call(*args)
41
+ end
42
+ end
43
+
44
+ # Default case
45
+ form
46
+ end
47
+ end
@@ -0,0 +1,11 @@
1
+ # This file contains some things that are used in a bunch of places and don't
2
+ # fit anywhere in particular.
3
+
4
+ module Apricot
5
+ # TODO: Should this counter be thread-local?
6
+ @gensym = 0
7
+
8
+ def self.gensym(prefix = 'g')
9
+ :"#{prefix}__#{@gensym += 1}"
10
+ end
11
+ end
@@ -0,0 +1,59 @@
1
+ module Apricot
2
+ class Namespace < Module
3
+ def self.find_or_create(constant)
4
+ ns = constant.const_names.reduce(Object) do |mod, name|
5
+ if mod.const_defined? name
6
+ next_mod = mod.const_get name
7
+ raise TypeError, "#{mod}::#{name} (#{next_mod}) is not a Module" unless next_mod.is_a? Module
8
+ next_mod
9
+ else
10
+ mod.const_set(name, Namespace.new)
11
+ end
12
+ end
13
+
14
+ raise TypeError, "#{constant.name} is not a Namespace" unless ns.is_a? Namespace
15
+
16
+ ns
17
+ end
18
+
19
+ attr_reader :vars, :fns
20
+
21
+ def initialize
22
+ @vars = {}
23
+ @fns = Set[]
24
+ end
25
+
26
+ def set_var(name, val)
27
+ @vars[name] = val
28
+
29
+ val = val.to_proc if val.is_a? Method
30
+
31
+ if val.is_a?(Proc) && (@fns.include?(name) || !self.respond_to?(name))
32
+ @fns.add name
33
+ define_singleton_method(name, val)
34
+ elsif @fns.include?(name)
35
+ @fns.delete name
36
+ singleton_class.send(:undef_method, name)
37
+ end
38
+
39
+ val
40
+ end
41
+
42
+ def get_var(name)
43
+ # raise may be a function defined on the namespace so we need to
44
+ # explicitly call the Ruby raise method.
45
+ Kernel.raise NameError, "Undefined variable '#{name}' on #{self}" unless @vars.include? name
46
+ @vars[name]
47
+ end
48
+ end
49
+
50
+ class << self
51
+ def current_namespace
52
+ Core.get_var(:"*ns*")
53
+ end
54
+
55
+ def current_namespace=(ns)
56
+ Core.set_var(:"*ns*", ns)
57
+ end
58
+ end
59
+ end