apricot 0.0.1 → 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -0
- data/Gemfile.lock +229 -11
- data/README.md +46 -29
- data/Rakefile +1 -1
- data/apricot.gemspec +7 -3
- data/benchmarks/factorial.rb +51 -0
- data/benchmarks/interpolate.rb +20 -0
- data/bin/apricot +5 -23
- data/examples/bot.apr +1 -4
- data/examples/cinch-bot.apr +3 -3
- data/examples/sinatra.apr +9 -0
- data/kernel/core.apr +124 -75
- data/kernel/repl.apr +37 -0
- data/lib/apricot.rb +7 -26
- data/lib/apricot/boot.rb +24 -0
- data/lib/apricot/code_loader.rb +108 -0
- data/lib/apricot/compiler.rb +265 -32
- data/lib/apricot/generator.rb +10 -3
- data/lib/apricot/identifier.rb +25 -10
- data/lib/apricot/list.rb +28 -41
- data/lib/apricot/macroexpand.rb +14 -8
- data/lib/apricot/misc.rb +2 -1
- data/lib/apricot/namespace.rb +20 -3
- data/lib/apricot/{parser.rb → reader.rb} +221 -194
- data/lib/apricot/repl.rb +67 -24
- data/lib/apricot/ruby_ext.rb +27 -16
- data/lib/apricot/scopes.rb +159 -0
- data/lib/apricot/seq.rb +43 -1
- data/lib/apricot/special_forms.rb +16 -695
- data/lib/apricot/special_forms/def.rb +32 -0
- data/lib/apricot/special_forms/do.rb +23 -0
- data/lib/apricot/special_forms/dot.rb +112 -0
- data/lib/apricot/special_forms/fn.rb +342 -0
- data/lib/apricot/special_forms/if.rb +31 -0
- data/lib/apricot/special_forms/let.rb +8 -0
- data/lib/apricot/special_forms/loop.rb +10 -0
- data/lib/apricot/special_forms/quote.rb +9 -0
- data/lib/apricot/special_forms/recur.rb +26 -0
- data/lib/apricot/special_forms/try.rb +146 -0
- data/lib/apricot/variables.rb +65 -0
- data/lib/apricot/version.rb +1 -1
- data/spec/compiler_spec.rb +53 -450
- data/spec/fn_spec.rb +206 -0
- data/spec/list_spec.rb +1 -1
- data/spec/reader_spec.rb +349 -0
- data/spec/spec_helper.rb +40 -4
- data/spec/special_forms_spec.rb +203 -0
- metadata +99 -133
- data/lib/apricot/ast.rb +0 -3
- data/lib/apricot/ast/identifier.rb +0 -111
- data/lib/apricot/ast/list.rb +0 -99
- data/lib/apricot/ast/literals.rb +0 -240
- data/lib/apricot/ast/node.rb +0 -45
- data/lib/apricot/ast/scopes.rb +0 -147
- data/lib/apricot/ast/toplevel.rb +0 -66
- data/lib/apricot/ast/variables.rb +0 -64
- data/lib/apricot/printers.rb +0 -12
- data/lib/apricot/stages.rb +0 -60
- data/spec/parser_spec.rb +0 -312
data/lib/apricot/ast/node.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
module Apricot
|
2
|
-
module AST
|
3
|
-
class Node
|
4
|
-
attr_reader :line
|
5
|
-
|
6
|
-
def initialize(line)
|
7
|
-
@line = line
|
8
|
-
end
|
9
|
-
|
10
|
-
def pos(g)
|
11
|
-
g.set_line(@line)
|
12
|
-
end
|
13
|
-
|
14
|
-
def ==(other)
|
15
|
-
return true if self.equal?(other)
|
16
|
-
return false unless self.class == other.class
|
17
|
-
self.node_equal?(other)
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.from_value(val, line = 0)
|
21
|
-
case val
|
22
|
-
when true then Literal.new(line, :true)
|
23
|
-
when false then Literal.new(line, :false)
|
24
|
-
when nil then Literal.new(line, :nil)
|
25
|
-
when Integer then AST.new_integer(line, val)
|
26
|
-
when Symbol then SymbolLiteral.new(line, val)
|
27
|
-
when Float then FloatLiteral.new(line, val)
|
28
|
-
when String then StringLiteral.new(line, val)
|
29
|
-
when Rational then RationalLiteral.new(line, val.numerator, val.denominator)
|
30
|
-
when Regexp then RegexLiteral.new(line, val.source, val.options)
|
31
|
-
when Array then ArrayLiteral.new(line, val.map {|x| from_value x, line})
|
32
|
-
when Set then SetLiteral.new(line, val.map {|x| from_value x, line})
|
33
|
-
when Apricot::Identifier then Identifier.new(line, val.name)
|
34
|
-
when Apricot::Seq then List.new(line, val.map {|x| from_value x, line})
|
35
|
-
when Hash
|
36
|
-
elems = []
|
37
|
-
val.each_pair {|k,v| elems << from_value(k, line) << from_value(v, line) }
|
38
|
-
HashLiteral.new(line, elems)
|
39
|
-
else
|
40
|
-
raise TypeError, "No AST node for #{val} (#{val.class})"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
data/lib/apricot/ast/scopes.rb
DELETED
@@ -1,147 +0,0 @@
|
|
1
|
-
module Apricot
|
2
|
-
module AST
|
3
|
-
# This is a scope with real local variable storage, i.e. it is part of a
|
4
|
-
# block of code like a fn or the top level program. Let scopes do not have
|
5
|
-
# storage and must ask for storage from one of these.
|
6
|
-
module StorageScope
|
7
|
-
def variable_names
|
8
|
-
@variable_names ||= []
|
9
|
-
end
|
10
|
-
|
11
|
-
def store_new_local(name)
|
12
|
-
slot = next_slot
|
13
|
-
variable_names << name
|
14
|
-
slot
|
15
|
-
end
|
16
|
-
|
17
|
-
def next_slot
|
18
|
-
variable_names.size
|
19
|
-
end
|
20
|
-
|
21
|
-
def local_count
|
22
|
-
variable_names.size
|
23
|
-
end
|
24
|
-
|
25
|
-
def local_names
|
26
|
-
variable_names
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
class Scope
|
31
|
-
attr_reader :parent, :variables
|
32
|
-
# The loop label stores the code location where a (recur) form should
|
33
|
-
# jump to. The secondary loop label is used in the case of recur in a fn
|
34
|
-
# overload with variadic arguments. If the array passed for the variadic
|
35
|
-
# arguments in the recur is empty, it should instead jump to the
|
36
|
-
# matching non-variadic overload, if applicable.
|
37
|
-
attr_accessor :loop_label, :secondary_loop_label
|
38
|
-
|
39
|
-
def initialize(parent)
|
40
|
-
@parent = parent
|
41
|
-
@variables = {}
|
42
|
-
@loop_label = nil
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class FnScope < Scope
|
47
|
-
attr_reader :name, :self_reference
|
48
|
-
|
49
|
-
def initialize(parent, name)
|
50
|
-
super(parent)
|
51
|
-
|
52
|
-
if name
|
53
|
-
@name = name
|
54
|
-
name_slot = @parent.store_new_local(name)
|
55
|
-
@self_reference = LocalReference.new(name_slot, 1)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# An identifier or a nested scope is looking up a variable. If the
|
60
|
-
# variable is found here, return a reference to it. Otherwise look it up
|
61
|
-
# on the parent and increment its depth because it is beyond the bounds
|
62
|
-
# of the current block of code (fn).
|
63
|
-
def find_var(name, depth = 0)
|
64
|
-
return @self_reference if name == @name
|
65
|
-
|
66
|
-
@parent.find_var(name, depth + 1)
|
67
|
-
end
|
68
|
-
|
69
|
-
# A (recur) is looking for a recursion target (ie. a loop or a fn
|
70
|
-
# overload scope).
|
71
|
-
def find_recur_target
|
72
|
-
@parent.find_recur_target
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
class OverloadScope < Scope
|
77
|
-
include StorageScope
|
78
|
-
|
79
|
-
attr_accessor :splat
|
80
|
-
alias_method :splat?, :splat
|
81
|
-
|
82
|
-
attr_accessor :block_arg
|
83
|
-
|
84
|
-
def initialize(parent_fn)
|
85
|
-
super(parent_fn)
|
86
|
-
end
|
87
|
-
|
88
|
-
# An identifier or a nested scope is looking up a variable. If the
|
89
|
-
# variable is found here, return a reference to it. Otherwise look it up
|
90
|
-
# on the parent (a fn). Don't increase the depth, the lookup on the fn
|
91
|
-
# will do that, and if we do it twice then the generated
|
92
|
-
# push_local_depth instructions look up too many scopes.
|
93
|
-
def find_var(name, depth = 0)
|
94
|
-
if slot = @variables[name]
|
95
|
-
LocalReference.new(slot, depth)
|
96
|
-
else
|
97
|
-
@parent.find_var(name, depth)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# Create a new local on the current level.
|
102
|
-
def new_local(name)
|
103
|
-
name = name.name if name.is_a? Identifier
|
104
|
-
@variables[name] = store_new_local(name)
|
105
|
-
end
|
106
|
-
|
107
|
-
# A (recur) is looking for a recursion target. This, being a fn
|
108
|
-
# overload, is one.
|
109
|
-
def find_recur_target
|
110
|
-
self
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
# The let scope doesn't have real storage for locals. It stores its locals
|
115
|
-
# on the nearest enclosing real scope, which is any separate block of code
|
116
|
-
# such as a fn, defn, defmacro or the top level of the program.
|
117
|
-
class LetScope < Scope
|
118
|
-
# An identifier or a nested scope is looking up a variable.
|
119
|
-
def find_var(name, depth = 0)
|
120
|
-
if slot = @variables[name]
|
121
|
-
LocalReference.new(slot, depth)
|
122
|
-
else
|
123
|
-
@parent.find_var(name, depth)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
# Create a new local on the current level, with storage on the nearest
|
128
|
-
# enclosing real scope.
|
129
|
-
def new_local(name)
|
130
|
-
name = name.name if name.is_a? Identifier
|
131
|
-
@variables[name] = @parent.store_new_local(name)
|
132
|
-
end
|
133
|
-
|
134
|
-
# A deeper let is asking for a new local slot. Pass it along to the
|
135
|
-
# parent so it eventually reaches a real scope.
|
136
|
-
def store_new_local(name)
|
137
|
-
@parent.store_new_local(name)
|
138
|
-
end
|
139
|
-
|
140
|
-
# A (recur) is looking for a recursion target. This is one only if it is
|
141
|
-
# a (loop) form.
|
142
|
-
def find_recur_target
|
143
|
-
loop_label ? self : @parent.find_recur_target
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
data/lib/apricot/ast/toplevel.rb
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
module Apricot
|
2
|
-
module AST
|
3
|
-
class TopLevel < Node
|
4
|
-
include StorageScope
|
5
|
-
|
6
|
-
attr_reader :elements, :file
|
7
|
-
|
8
|
-
def initialize(elements, file, line = 1, evaluate = false)
|
9
|
-
@elements = elements
|
10
|
-
@file = file
|
11
|
-
@line = line
|
12
|
-
@evaluate = evaluate
|
13
|
-
end
|
14
|
-
|
15
|
-
def bytecode(g)
|
16
|
-
g.name = :__top_level__
|
17
|
-
g.file = @file.to_sym
|
18
|
-
|
19
|
-
g.scopes << self
|
20
|
-
|
21
|
-
pos(g)
|
22
|
-
|
23
|
-
if @elements.empty?
|
24
|
-
g.push_nil
|
25
|
-
else
|
26
|
-
@elements.each_with_index do |e, i|
|
27
|
-
g.pop unless i == 0
|
28
|
-
e.bytecode(g)
|
29
|
-
# We evaluate top level forms as we generate the bytecode for them
|
30
|
-
# so macros defined in a file can be used immediately after the
|
31
|
-
# definition.
|
32
|
-
Apricot::Compiler.eval_node(e, @file) if @evaluate
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
g.ret
|
37
|
-
|
38
|
-
g.scopes.pop
|
39
|
-
|
40
|
-
g.local_count = local_count
|
41
|
-
g.local_names = local_names
|
42
|
-
end
|
43
|
-
|
44
|
-
# A nested scope is looking up a variable. There are no local variables
|
45
|
-
# at the top level, so look up the variable on the current namespace.
|
46
|
-
def find_var(name, depth = nil)
|
47
|
-
# Ignore depth, it has no bearing on namespace lookups.
|
48
|
-
NamespaceReference.new(name)
|
49
|
-
end
|
50
|
-
|
51
|
-
def node_equal?(other)
|
52
|
-
self.file == other.file && self.elements == other.elements
|
53
|
-
end
|
54
|
-
|
55
|
-
def [](*i)
|
56
|
-
@elements[*i]
|
57
|
-
end
|
58
|
-
|
59
|
-
# A (recur) is looking for a recursion target. Since this is the top
|
60
|
-
# level, which has no parent, the lookup has failed.
|
61
|
-
def find_recur_target
|
62
|
-
nil
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
@@ -1,64 +0,0 @@
|
|
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/printers.rb
DELETED
data/lib/apricot/stages.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
module Apricot
|
2
|
-
class Compiler
|
3
|
-
class Generator < Rubinius::Compiler::Stage
|
4
|
-
stage :apricot_bytecode
|
5
|
-
next_stage Rubinius::Compiler::Encoder
|
6
|
-
|
7
|
-
def initialize(compiler, last)
|
8
|
-
super
|
9
|
-
compiler.generator = self
|
10
|
-
end
|
11
|
-
|
12
|
-
def run
|
13
|
-
@output = Apricot::Generator.new
|
14
|
-
@input.bytecode @output
|
15
|
-
@output.close
|
16
|
-
run_next
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
class Parser < Rubinius::Compiler::Stage
|
21
|
-
def initialize(compiler, last)
|
22
|
-
super
|
23
|
-
compiler.parser = self
|
24
|
-
end
|
25
|
-
|
26
|
-
def run
|
27
|
-
@output = parse
|
28
|
-
run_next
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class FileParser < Parser
|
33
|
-
stage :apricot_file
|
34
|
-
next_stage Generator
|
35
|
-
|
36
|
-
def input(file)
|
37
|
-
@file = file
|
38
|
-
end
|
39
|
-
|
40
|
-
def parse
|
41
|
-
Apricot::Parser.parse_file(@file)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class StringParser < Parser
|
46
|
-
stage :apricot_string
|
47
|
-
next_stage Generator
|
48
|
-
|
49
|
-
def input(code, file = "(none)", line = 1)
|
50
|
-
@input = code
|
51
|
-
@file = file
|
52
|
-
@line = line
|
53
|
-
end
|
54
|
-
|
55
|
-
def parse
|
56
|
-
Apricot::Parser.parse_string(@input, @file, @line)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
data/spec/parser_spec.rb
DELETED
@@ -1,312 +0,0 @@
|
|
1
|
-
describe Apricot::Parser do
|
2
|
-
def parse(s)
|
3
|
-
@ast = described_class.parse_string(s, "(spec)")
|
4
|
-
@first = @ast.elements.first
|
5
|
-
@ast.elements
|
6
|
-
end
|
7
|
-
|
8
|
-
def parse_one(s, klass)
|
9
|
-
parse(s).length.should == 1
|
10
|
-
@first.should be_a(klass)
|
11
|
-
@first
|
12
|
-
end
|
13
|
-
|
14
|
-
def expect_syntax_error(s)
|
15
|
-
expect { parse(s) }.to raise_error(Apricot::SyntaxError)
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'parses nothing' do
|
19
|
-
parse('').should be_empty
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'skips whitespace' do
|
23
|
-
parse(" \n\t,").should be_empty
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'skips comments' do
|
27
|
-
parse('; example').should be_empty
|
28
|
-
parse('#!/usr/bin/env apricot').should be_empty
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'discards commented forms' do
|
32
|
-
parse('#_form').should be_empty
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'parses identifiers' do
|
36
|
-
parse_one('example', AST::Identifier)
|
37
|
-
@first.name.should == :example
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'parses pipe identifiers' do
|
41
|
-
parse_one('#|example|', AST::Identifier).name.should == :example
|
42
|
-
parse_one('#|foo bar|', AST::Identifier).name.should == :"foo bar"
|
43
|
-
parse_one('#|foo\nbar|', AST::Identifier).name.should == :"foo\nbar"
|
44
|
-
parse_one('#|foo\|bar|', AST::Identifier).name.should == :"foo|bar"
|
45
|
-
parse_one('#|foo"bar|', AST::Identifier).name.should == :'foo"bar'
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'parses constants' do
|
49
|
-
parse_one('Example', AST::Identifier)
|
50
|
-
@first.constant?.should be_true
|
51
|
-
@first.const_names.should == [:Example]
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'parses invalid constants as identifiers' do
|
55
|
-
parse_one('Fo$o', AST::Identifier)
|
56
|
-
@first.constant?.should be_false
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'parses scoped constants' do
|
60
|
-
parse_one('Foo::Bar::Baz', AST::Identifier)
|
61
|
-
@first.constant?.should be_true
|
62
|
-
@first.const_names.should == [:Foo, :Bar, :Baz]
|
63
|
-
end
|
64
|
-
|
65
|
-
it 'parses invalid scoped constants as identifiers' do
|
66
|
-
parse_one('Foo::', AST::Identifier)
|
67
|
-
@first.constant?.should be_false
|
68
|
-
parse_one('Foo:', AST::Identifier)
|
69
|
-
@first.constant?.should be_false
|
70
|
-
parse_one('Foo::a', AST::Identifier)
|
71
|
-
@first.constant?.should be_false
|
72
|
-
parse_one('Foo::::Bar', AST::Identifier)
|
73
|
-
@first.constant?.should be_false
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'parses true, false, nil, and self' do
|
77
|
-
parse('true false nil self').length.should == 4
|
78
|
-
@ast[0].should be_a(AST::Literal)
|
79
|
-
@ast[0].value.should == :true
|
80
|
-
@ast[1].should be_a(AST::Literal)
|
81
|
-
@ast[1].value.should == :false
|
82
|
-
@ast[2].should be_a(AST::Literal)
|
83
|
-
@ast[2].value.should == :nil
|
84
|
-
@ast[3].should be_a(AST::Identifier)
|
85
|
-
@ast[3].name.should == :self
|
86
|
-
end
|
87
|
-
|
88
|
-
it 'parses fixnums' do
|
89
|
-
parse_one('123', AST::Literal)
|
90
|
-
@first.value.should == 123
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'parses bignums' do
|
94
|
-
parse_one('12345678901234567890', AST::BignumLiteral)
|
95
|
-
@first.value.should == 12345678901234567890
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'parses radix integers' do
|
99
|
-
parse_one('2r10', AST::Literal)
|
100
|
-
@first.value.should == 2
|
101
|
-
end
|
102
|
-
|
103
|
-
it 'parses floats' do
|
104
|
-
parse_one('1.23', AST::FloatLiteral)
|
105
|
-
@first.value.should == 1.23
|
106
|
-
end
|
107
|
-
|
108
|
-
it 'parses rationals' do
|
109
|
-
parse_one('12/34', AST::RationalLiteral)
|
110
|
-
@first.numerator.should == 12
|
111
|
-
@first.denominator.should == 34
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'does not parse invalid numbers' do
|
115
|
-
expect_syntax_error '12abc'
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'parses empty strings' do
|
119
|
-
parse_one('""', AST::StringLiteral)
|
120
|
-
@first.value.should == ''
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'parses strings' do
|
124
|
-
parse_one('"Hello, world!"', AST::StringLiteral)
|
125
|
-
@first.value.should == 'Hello, world!'
|
126
|
-
end
|
127
|
-
|
128
|
-
it 'parses multiline strings' do
|
129
|
-
parse_one(%{"This is\na test"}, AST::StringLiteral)
|
130
|
-
@first.value.should == "This is\na test"
|
131
|
-
end
|
132
|
-
|
133
|
-
it 'does not parse unfinished strings' do
|
134
|
-
expect_syntax_error '"'
|
135
|
-
end
|
136
|
-
|
137
|
-
it 'parses strings with character escapes' do
|
138
|
-
parse_one('"\\a\\b\\t\\n\\v\\f\\r\\e\\"\\\\"', AST::StringLiteral)
|
139
|
-
@first.value.should == "\a\b\t\n\v\f\r\e\"\\"
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'parses strings with octal escapes' do
|
143
|
-
parse_one('"\\1\\01\\001"', AST::StringLiteral)
|
144
|
-
@first.value.should == "\001\001\001"
|
145
|
-
end
|
146
|
-
|
147
|
-
it 'parses strings with hex escapes' do
|
148
|
-
parse_one('"\\x1\\x01"', AST::StringLiteral)
|
149
|
-
@first.value.should == "\001\001"
|
150
|
-
end
|
151
|
-
|
152
|
-
it 'does not parse strings with invalid hex escapes' do
|
153
|
-
expect_syntax_error '"\\x"'
|
154
|
-
end
|
155
|
-
|
156
|
-
it 'stops parsing hex/octal escapes in strings at non-hex/octal digits' do
|
157
|
-
parse_one('"\xAZ\082"', AST::StringLiteral)
|
158
|
-
@first.value.should == "\x0AZ\00082"
|
159
|
-
end
|
160
|
-
|
161
|
-
it 'parses regexes' do
|
162
|
-
parse_one('#r!!', AST::RegexLiteral).pattern.should == ''
|
163
|
-
parse_one('#r!egex!', AST::RegexLiteral).pattern.should == 'egex'
|
164
|
-
parse_one('#r(egex)', AST::RegexLiteral).pattern.should == 'egex'
|
165
|
-
parse_one('#r[egex]', AST::RegexLiteral).pattern.should == 'egex'
|
166
|
-
parse_one('#r{egex}', AST::RegexLiteral).pattern.should == 'egex'
|
167
|
-
parse_one('#r<egex>', AST::RegexLiteral).pattern.should == 'egex'
|
168
|
-
parse_one('#r!\!!', AST::RegexLiteral).pattern.should == '!'
|
169
|
-
parse_one('#r!foo\bar!', AST::RegexLiteral).pattern.should == 'foo\bar'
|
170
|
-
parse_one('#r!\\\\!', AST::RegexLiteral).pattern.should == "\\\\"
|
171
|
-
end
|
172
|
-
|
173
|
-
it 'parses regexes with trailing options' do
|
174
|
-
parse_one('#r//i', AST::RegexLiteral)
|
175
|
-
@first.options.should == Regexp::IGNORECASE
|
176
|
-
parse_one('#r/foo/x', AST::RegexLiteral)
|
177
|
-
@first.options.should == Regexp::EXTENDED
|
178
|
-
parse_one('#r//im', AST::RegexLiteral)
|
179
|
-
@first.options.should == Regexp::IGNORECASE | Regexp::MULTILINE
|
180
|
-
end
|
181
|
-
|
182
|
-
it 'does not parse regexes with unknown trailing options' do
|
183
|
-
expect_syntax_error '#r/foo/asdf'
|
184
|
-
end
|
185
|
-
|
186
|
-
it 'parses symbols' do
|
187
|
-
parse_one(':example', AST::SymbolLiteral)
|
188
|
-
@first.value.should == :example
|
189
|
-
end
|
190
|
-
|
191
|
-
it 'parses quoted symbols' do
|
192
|
-
parse_one(':"\x01()"', AST::SymbolLiteral)
|
193
|
-
@first.value.should == :"\x01()"
|
194
|
-
end
|
195
|
-
|
196
|
-
it 'does not parse unfinished quoted symbols' do
|
197
|
-
expect_syntax_error ':"'
|
198
|
-
end
|
199
|
-
|
200
|
-
it 'does not parse empty symbols' do
|
201
|
-
expect_syntax_error ':'
|
202
|
-
end
|
203
|
-
|
204
|
-
it 'does parse empty quoted symbols' do
|
205
|
-
parse_one(':""', AST::SymbolLiteral)
|
206
|
-
@first.value.should == :""
|
207
|
-
end
|
208
|
-
|
209
|
-
it 'parses empty lists' do
|
210
|
-
parse_one('()', AST::List)
|
211
|
-
@first.elements.should be_empty
|
212
|
-
end
|
213
|
-
|
214
|
-
it 'parses lists' do
|
215
|
-
parse_one('(1 two)', AST::List)
|
216
|
-
@first[0].should be_a(AST::Literal)
|
217
|
-
@first[1].should be_a(AST::Identifier)
|
218
|
-
end
|
219
|
-
|
220
|
-
it 'parses empty arrays' do
|
221
|
-
parse_one('[]', AST::ArrayLiteral)
|
222
|
-
@first.elements.should be_empty
|
223
|
-
end
|
224
|
-
|
225
|
-
it 'parses arrays' do
|
226
|
-
parse_one('[1 two]', AST::ArrayLiteral)
|
227
|
-
@first[0].should be_a(AST::Literal)
|
228
|
-
@first[1].should be_a(AST::Identifier)
|
229
|
-
end
|
230
|
-
|
231
|
-
it 'parses empty hashes' do
|
232
|
-
parse_one('{}', AST::HashLiteral)
|
233
|
-
@first.elements.should be_empty
|
234
|
-
end
|
235
|
-
|
236
|
-
it 'parses hashes' do
|
237
|
-
parse_one('{:example 1}', AST::HashLiteral)
|
238
|
-
@first[0].should be_a(AST::SymbolLiteral)
|
239
|
-
@first[1].should be_a(AST::Literal)
|
240
|
-
end
|
241
|
-
|
242
|
-
it 'does not parse invalid hashes' do
|
243
|
-
expect_syntax_error '{:foo 1 :bar}'
|
244
|
-
end
|
245
|
-
|
246
|
-
it 'parses empty sets' do
|
247
|
-
parse_one('#{}', AST::SetLiteral)
|
248
|
-
@first.elements.should be_empty
|
249
|
-
end
|
250
|
-
|
251
|
-
it 'parses sets' do
|
252
|
-
parse_one('#{1 two}', AST::SetLiteral)
|
253
|
-
@first[0].should be_a(AST::Literal)
|
254
|
-
@first[1].should be_a(AST::Identifier)
|
255
|
-
end
|
256
|
-
|
257
|
-
it 'parses multiple forms' do
|
258
|
-
parse('foo bar').length.should == 2
|
259
|
-
@ast[0].should be_a(AST::Identifier)
|
260
|
-
@ast[1].should be_a(AST::Identifier)
|
261
|
-
end
|
262
|
-
|
263
|
-
it 'parses quoted forms' do
|
264
|
-
parse_one("'test", AST::List)
|
265
|
-
@first.elements.length.should == 2
|
266
|
-
@first[0].should be_a(AST::Identifier)
|
267
|
-
@first[0].name.should == :quote
|
268
|
-
@first[1].should be_a(AST::Identifier)
|
269
|
-
@first[1].name.should == :test
|
270
|
-
end
|
271
|
-
|
272
|
-
it 'parses syntax quoted forms' do
|
273
|
-
begin
|
274
|
-
old_gensym = Apricot.instance_variable_get :@gensym
|
275
|
-
Apricot.instance_variable_set :@gensym, 41
|
276
|
-
|
277
|
-
parse_one("`(foo ~bar ~@baz quux#)", AST::List)
|
278
|
-
@first.should == AST::Node.from_value(
|
279
|
-
List[Identifier.intern(:concat),
|
280
|
-
List[Identifier.intern(:list),
|
281
|
-
List[Identifier.intern(:quote),
|
282
|
-
Identifier.intern(:foo)]],
|
283
|
-
List[Identifier.intern(:list),
|
284
|
-
Identifier.intern(:bar)],
|
285
|
-
Identifier.intern(:baz),
|
286
|
-
List[Identifier.intern(:list),
|
287
|
-
List[Identifier.intern(:quote),
|
288
|
-
Identifier.intern(:'quux#__42')]]])
|
289
|
-
ensure
|
290
|
-
Apricot.instance_variable_set :@gensym, old_gensym
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
it 'parses #() shorthand' do
|
295
|
-
Apricot.stub(:gensym).and_return(*:a..:z)
|
296
|
-
|
297
|
-
parse("#()").should == parse("(fn [] ())")
|
298
|
-
parse("#(foo)").should == parse("(fn [] (foo))")
|
299
|
-
parse("#(%)").should == parse("(fn [a] (a))")
|
300
|
-
parse("#(% %2)").should == parse("(fn [b c] (b c))")
|
301
|
-
parse("#(%1 %2)").should == parse("(fn [d e] (d e))")
|
302
|
-
parse("#(%2)").should == parse("(fn [g f] (f))")
|
303
|
-
parse("#(%&)").should == parse("(fn [& h] (h))")
|
304
|
-
parse("#(% %&)").should == parse("(fn [i & j] (i j))")
|
305
|
-
|
306
|
-
expect_syntax_error("#(%0)")
|
307
|
-
expect_syntax_error("#(%-1)")
|
308
|
-
expect_syntax_error("#(%x)")
|
309
|
-
expect_syntax_error("#(%1.1)")
|
310
|
-
expect_syntax_error("#(%1asdf)")
|
311
|
-
end
|
312
|
-
end
|