apricot 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +26 -0
- data/README.md +90 -0
- data/Rakefile +9 -0
- data/apricot.gemspec +22 -0
- data/bin/apricot +58 -0
- data/examples/bot.apr +23 -0
- data/examples/cinch-bot.apr +12 -0
- data/examples/hanoi.apr +10 -0
- data/examples/hello.apr +1 -0
- data/examples/plot.apr +28 -0
- data/examples/quine.apr +1 -0
- data/kernel/core.apr +928 -0
- data/lib/apricot/ast/identifier.rb +111 -0
- data/lib/apricot/ast/list.rb +99 -0
- data/lib/apricot/ast/literals.rb +240 -0
- data/lib/apricot/ast/node.rb +45 -0
- data/lib/apricot/ast/scopes.rb +147 -0
- data/lib/apricot/ast/toplevel.rb +66 -0
- data/lib/apricot/ast/variables.rb +64 -0
- data/lib/apricot/ast.rb +3 -0
- data/lib/apricot/compiler.rb +55 -0
- data/lib/apricot/cons.rb +27 -0
- data/lib/apricot/errors.rb +38 -0
- data/lib/apricot/generator.rb +15 -0
- data/lib/apricot/identifier.rb +91 -0
- data/lib/apricot/list.rb +96 -0
- data/lib/apricot/macroexpand.rb +47 -0
- data/lib/apricot/misc.rb +11 -0
- data/lib/apricot/namespace.rb +59 -0
- data/lib/apricot/parser.rb +541 -0
- data/lib/apricot/printers.rb +12 -0
- data/lib/apricot/repl.rb +254 -0
- data/lib/apricot/ruby_ext.rb +254 -0
- data/lib/apricot/seq.rb +44 -0
- data/lib/apricot/special_forms.rb +735 -0
- data/lib/apricot/stages.rb +60 -0
- data/lib/apricot/version.rb +3 -0
- data/lib/apricot.rb +30 -0
- data/spec/compiler_spec.rb +499 -0
- data/spec/identifier_spec.rb +58 -0
- data/spec/list_spec.rb +96 -0
- data/spec/parser_spec.rb +312 -0
- data/spec/spec_helper.rb +10 -0
- 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
|
data/lib/apricot/ast.rb
ADDED
@@ -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
|
data/lib/apricot/cons.rb
ADDED
@@ -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,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
|
data/lib/apricot/list.rb
ADDED
@@ -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
|
data/lib/apricot/misc.rb
ADDED
@@ -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
|