twostroke 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.
- data/lib/twostroke/ast/array.rb +14 -0
- data/lib/twostroke/ast/assignment.rb +9 -0
- data/lib/twostroke/ast/binary_operators.rb +13 -0
- data/lib/twostroke/ast/body.rb +14 -0
- data/lib/twostroke/ast/call.rb +14 -0
- data/lib/twostroke/ast/declaration.rb +9 -0
- data/lib/twostroke/ast/for_loop.rb +9 -0
- data/lib/twostroke/ast/function.rb +15 -0
- data/lib/twostroke/ast/if.rb +10 -0
- data/lib/twostroke/ast/index.rb +9 -0
- data/lib/twostroke/ast/member_access.rb +9 -0
- data/lib/twostroke/ast/multi_expression.rb +9 -0
- data/lib/twostroke/ast/number.rb +9 -0
- data/lib/twostroke/ast/object_literal.rb +15 -0
- data/lib/twostroke/ast/return.rb +9 -0
- data/lib/twostroke/ast/string.rb +9 -0
- data/lib/twostroke/ast/ternary.rb +9 -0
- data/lib/twostroke/ast/unary_operators.rb +13 -0
- data/lib/twostroke/ast/unsorted_binop.rb +99 -0
- data/lib/twostroke/ast/variable.rb +9 -0
- data/lib/twostroke/ast.rb +15 -0
- data/lib/twostroke/error.rb +4 -0
- data/lib/twostroke/lexer.rb +51 -0
- data/lib/twostroke/parser.rb +379 -0
- data/lib/twostroke/tokens.rb +78 -0
- data/lib/twostroke.rb +5 -0
- metadata +72 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
module Twostroke::AST
|
2
|
+
%w( Addition Subtraction Multiplication Division Modulus
|
3
|
+
LeftShift RightArithmeticShift RightLogicalShift
|
4
|
+
LessThan LessThanEqual GreaterThan GreaterThanEqual
|
5
|
+
In InstanceOf Equality Inequality StrictEquality
|
6
|
+
StrictInequality BitwiseAnd BitwiseXor BitwiseOr
|
7
|
+
And Or).each do |op|
|
8
|
+
klass = Class.new Base do
|
9
|
+
attr_accessor :left, :right
|
10
|
+
end
|
11
|
+
const_set op, klass
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Twostroke::AST
|
2
|
+
class Call < Base
|
3
|
+
attr_accessor :callee, :arguments
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
@arguments = []
|
7
|
+
super *args
|
8
|
+
end
|
9
|
+
|
10
|
+
def collapse
|
11
|
+
self.class.new callee: callee.collapse, arguments: arguments.map(&:collapse)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Twostroke::AST
|
2
|
+
class ForLoop < Base
|
3
|
+
attr_accessor :initializer, :condition, :increment, :body
|
4
|
+
|
5
|
+
def collapse
|
6
|
+
self.class.new initializer: initializer.collapse, condition: condition.collapse, increment: increment.collapse, body: body.collapse
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Twostroke::AST
|
2
|
+
class Function < Base
|
3
|
+
attr_accessor :name, :arguments, :statements
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
@arguments = []
|
7
|
+
@statements = []
|
8
|
+
super *args
|
9
|
+
end
|
10
|
+
|
11
|
+
def collapse
|
12
|
+
self.class.new name: name, arguments: arguments, statements: statements.reject(&:nil?).map(&:collapse)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Twostroke::AST
|
2
|
+
class ObjectLiteral < Base
|
3
|
+
attr_accessor :items
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
@items = []
|
7
|
+
super *args
|
8
|
+
end
|
9
|
+
|
10
|
+
def collapse
|
11
|
+
collapsed = items.map { |k,v| [k, v.collapse] }
|
12
|
+
self.class.new items: collapsed
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Twostroke::AST
|
2
|
+
[ :PostIncrement, :PreIncrement, :PostDecrement, :PreDecrement,
|
3
|
+
:BinaryNot, :UnaryPlus, :Negation, :TypeOf, :Not ].each do |op|
|
4
|
+
klass = Class.new Base do
|
5
|
+
attr_accessor :value
|
6
|
+
|
7
|
+
def collapse
|
8
|
+
self.class.new value: value.collapse
|
9
|
+
end
|
10
|
+
end
|
11
|
+
const_set op, klass
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Twostroke::AST
|
2
|
+
class UnsortedBinop < Base
|
3
|
+
attr_accessor :left, :op, :right
|
4
|
+
|
5
|
+
def self.operator_class
|
6
|
+
@@classes ||= {
|
7
|
+
:ASTERISK => Multiplication,
|
8
|
+
:SLASH => Division,
|
9
|
+
:MOD => Modulus,
|
10
|
+
:PLUS => Addition,
|
11
|
+
:MINUS => Subtraction,
|
12
|
+
:LEFT_SHIFT => LeftShift,
|
13
|
+
:RIGHT_SHIFT => RightArithmeticShift,
|
14
|
+
:RIGHT_TRIPLE_SHIFT => RightLogicalShift,
|
15
|
+
:LT => LessThan,
|
16
|
+
:LTE => LessThanEqual,
|
17
|
+
:GT => GreaterThan,
|
18
|
+
:GTE => GreaterThanEqual,
|
19
|
+
:IN => In,
|
20
|
+
:INSTANCE_OF => InstanceOf,
|
21
|
+
:DOUBLE_EQUALS => Equality,
|
22
|
+
:NOT_EQUALS => Inequality,
|
23
|
+
:TRIPLE_EQUALS => StrictEquality,
|
24
|
+
:NOT_DOUBLE_EQUALS => StrictInequality,
|
25
|
+
:AMPERSAND => BitwiseAnd,
|
26
|
+
:CARET => BitwiseXor,
|
27
|
+
:PIPE => BitwiseOr,
|
28
|
+
:AND => And,
|
29
|
+
:OR => Or
|
30
|
+
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.operator_precedence
|
35
|
+
@precedences ||= {
|
36
|
+
:ASTERISK => 5,
|
37
|
+
:SLASH => 5,
|
38
|
+
:MOD => 5,
|
39
|
+
:PLUS => 6,
|
40
|
+
:MINUS => 6,
|
41
|
+
:LEFT_SHIFT => 7,
|
42
|
+
:RIGHT_SHIFT => 7,
|
43
|
+
:RIGHT_TRIPLE_SHIFT => 7,
|
44
|
+
:LT => 8,
|
45
|
+
:LTE => 8,
|
46
|
+
:GT => 8,
|
47
|
+
:GTE => 8,
|
48
|
+
:IN => 8,
|
49
|
+
:INSTANCE_OF => 8,
|
50
|
+
:DOUBLE_EQUALS => 9,
|
51
|
+
:NOT_EQUALS => 9,
|
52
|
+
:TRIPLE_EQUALS => 9,
|
53
|
+
:NOT_DOUBLE_EQUALS => 9,
|
54
|
+
:AMPERSAND => 10,
|
55
|
+
:CARET => 11,
|
56
|
+
:PIPE => 12,
|
57
|
+
:AND => 13,
|
58
|
+
:OR => 14
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def collapse(called_by_binop = false)
|
63
|
+
left_collapsed = left.is_a?(UnsortedBinop) ? left.collapse(true) : left.collapse
|
64
|
+
right_collapsed = right.is_a?(UnsortedBinop) ? right.collapse(true) : right.collapse
|
65
|
+
input = [*left_collapsed, op, *right_collapsed]
|
66
|
+
|
67
|
+
unless called_by_binop
|
68
|
+
stack = []
|
69
|
+
output = []
|
70
|
+
input.each do |token|
|
71
|
+
if token.is_a? Symbol
|
72
|
+
while stack.size > 0 && UnsortedBinop.operator_precedence[stack.last] <= UnsortedBinop.operator_precedence[token]
|
73
|
+
output.push stack.pop
|
74
|
+
end
|
75
|
+
stack.push token
|
76
|
+
else
|
77
|
+
output.push token
|
78
|
+
end
|
79
|
+
end
|
80
|
+
output.push stack.pop until stack.empty?
|
81
|
+
|
82
|
+
output.each do |token|
|
83
|
+
if token.is_a? Symbol
|
84
|
+
r = stack.pop
|
85
|
+
l = stack.pop
|
86
|
+
puts token
|
87
|
+
stack.push UnsortedBinop.operator_class[token].new(left: l, right: r)
|
88
|
+
else
|
89
|
+
stack.push token
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
stack.last
|
94
|
+
else
|
95
|
+
input
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Twostroke
|
2
|
+
class LexError < Twostroke::Error
|
3
|
+
end
|
4
|
+
|
5
|
+
class Token
|
6
|
+
attr_accessor :type, :val, :line, :col
|
7
|
+
def initialize(hash = {})
|
8
|
+
hash.each do |k,v|
|
9
|
+
send "#{k}=", v
|
10
|
+
end
|
11
|
+
end
|
12
|
+
def is?(t)
|
13
|
+
if t.is_a? Array
|
14
|
+
t.include? type
|
15
|
+
else
|
16
|
+
t == type
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Lexer
|
22
|
+
attr_reader :str, :col, :line, :tokens
|
23
|
+
def initialize(str)
|
24
|
+
@str = str
|
25
|
+
@col = 1
|
26
|
+
@line = 1
|
27
|
+
@tokens = []
|
28
|
+
end
|
29
|
+
def lex
|
30
|
+
until @str.empty?
|
31
|
+
read_token
|
32
|
+
end
|
33
|
+
end
|
34
|
+
private
|
35
|
+
def read_token
|
36
|
+
TOKENS.each do |token|
|
37
|
+
m = token[1].match @str
|
38
|
+
if m
|
39
|
+
@tokens.push Token.new(:type => token[0], :val => token[2] ? token[2].call(m) : nil, :line => @line, :col => @col) unless [:WHITESPACE, :MULTI_COMMENT, :SINGLE_COMMENT].include? token[0]
|
40
|
+
@str = m.post_match
|
41
|
+
newlines = m[0].count "\n"
|
42
|
+
@col = 1 if !newlines.zero?
|
43
|
+
@line += newlines
|
44
|
+
@col += m[0].length - (m[0].rindex("\n") || 0)
|
45
|
+
return
|
46
|
+
end
|
47
|
+
end
|
48
|
+
raise LexError, "Illegal character '#{@str[0]}' at line #{@line}, col #{@col}. (read #{@tokens.count} tokens)"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,379 @@
|
|
1
|
+
module Twostroke
|
2
|
+
class ParseError < Error
|
3
|
+
end
|
4
|
+
|
5
|
+
class Parser
|
6
|
+
attr_reader :statements
|
7
|
+
|
8
|
+
def initialize(tokens)
|
9
|
+
@i = -1
|
10
|
+
@tokens = tokens + [Token.new(type: :SEMICOLON)]
|
11
|
+
@statements = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse
|
15
|
+
while @i + 1 < @tokens.length
|
16
|
+
st = statement
|
17
|
+
statements.push st.collapse if st
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def error!(msg)
|
23
|
+
raise ParseError, "Syntax error at line #{token.line}, col #{token.col}. #{msg}"
|
24
|
+
end
|
25
|
+
def assert_type(tok, *types)
|
26
|
+
error! "Found #{tok.type}#{"<#{tok.val}>" if tok.val}, expected #{types.join ", "}" unless types.include? tok.type
|
27
|
+
end
|
28
|
+
def stack
|
29
|
+
@stack
|
30
|
+
end
|
31
|
+
def stack_top
|
32
|
+
@stack.last
|
33
|
+
end
|
34
|
+
def token
|
35
|
+
@tokens[@i] or raise ParseError, "unexpected end of input"
|
36
|
+
end
|
37
|
+
def next_token
|
38
|
+
@i += 1
|
39
|
+
token
|
40
|
+
end
|
41
|
+
def try_peek_token
|
42
|
+
@i + 1 < @tokens.length ? peek_token : nil
|
43
|
+
end
|
44
|
+
def peek_token
|
45
|
+
@tokens[@i + 1] or raise ParseError, "unexpected end of input"
|
46
|
+
end
|
47
|
+
def look_ahead(n = 1)
|
48
|
+
@tokens[@i + n]
|
49
|
+
end
|
50
|
+
|
51
|
+
####################
|
52
|
+
|
53
|
+
def statement(consume_semicolon = true)
|
54
|
+
st = case peek_token.type
|
55
|
+
when :RETURN; send :return
|
56
|
+
when :VAR; var
|
57
|
+
when :IF; consume_semicolon = false; send :if
|
58
|
+
when :FOR; consume_semicolon = false; send :for
|
59
|
+
when :OPEN_BRACE; consume_semicolon = false; body
|
60
|
+
when :SEMICOLON; nil
|
61
|
+
else; expression
|
62
|
+
end
|
63
|
+
if consume_semicolon
|
64
|
+
#next_token if try_peek_token && peek_token.type == :SEMICOLON
|
65
|
+
assert_type next_token, :SEMICOLON
|
66
|
+
end
|
67
|
+
st
|
68
|
+
end
|
69
|
+
|
70
|
+
def expression(no_comma = false)
|
71
|
+
expr = expression_after_unary no_comma
|
72
|
+
if [:PLUS, :MINUS, :ASTERISK, :SLASH, :GT, :LT,
|
73
|
+
:GTE, :LTE, :DOUBLE_EQUALS, :TRIPLE_EQUALS,
|
74
|
+
:NOT_EQUALS, :NOT_DOUBLE_EQUALS, :AND, :OR,
|
75
|
+
:AMPERSAND, :PIPE, :CARET, :MOD, :LEFT_SHIFT,
|
76
|
+
:RIGHT_SHIFT, :RIGHT_TRIPLE_SHIFT, ].include? peek_token.type
|
77
|
+
binop expr
|
78
|
+
elsif peek_token.type == :EQUALS
|
79
|
+
next_token
|
80
|
+
AST::Assignment.new left: expr, right: expression(no_comma)
|
81
|
+
elsif peek_token.type == :QUESTION
|
82
|
+
ternary(expr)
|
83
|
+
else
|
84
|
+
expr
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def expression_after_unary(no_comma = false)
|
89
|
+
expr = case peek_token.type
|
90
|
+
when :FUNCTION; function
|
91
|
+
when :STRING; string
|
92
|
+
when :NUMBER; number
|
93
|
+
when :BAREWORD; bareword
|
94
|
+
when :OPEN_PAREN; parens
|
95
|
+
when :OPEN_BRACE; object_literal
|
96
|
+
when :OPEN_BRACKET; array
|
97
|
+
when :NOT; send :not
|
98
|
+
when :TILDE; tilde
|
99
|
+
when :INCREMENT; pre_increment
|
100
|
+
when :DECREMENT; pre_decrement
|
101
|
+
when :PLUS; unary_plus
|
102
|
+
when :MINUS; unary_minus
|
103
|
+
when :TYPEOF; typeof
|
104
|
+
else error! "Unexpected #{peek_token.type}"
|
105
|
+
end
|
106
|
+
loop do
|
107
|
+
if peek_token.type == :OPEN_PAREN
|
108
|
+
expr = call expr
|
109
|
+
elsif peek_token.type == :OPEN_BRACKET
|
110
|
+
expr = index expr
|
111
|
+
elsif peek_token.type == :MEMBER_ACCESS
|
112
|
+
expr = member_access expr
|
113
|
+
elsif !no_comma && peek_token.type == :COMMA
|
114
|
+
expr = comma(expr)
|
115
|
+
elsif peek_token.type == :INCREMENT
|
116
|
+
expr = post_increment expr
|
117
|
+
elsif peek_token.type == :DECREMENT
|
118
|
+
expr = post_decrement expr
|
119
|
+
else
|
120
|
+
return expr
|
121
|
+
end
|
122
|
+
end
|
123
|
+
expr
|
124
|
+
end
|
125
|
+
|
126
|
+
def binop(left)
|
127
|
+
next_token
|
128
|
+
AST::UnsortedBinop.new left: left, op: token.type, right: expression
|
129
|
+
end
|
130
|
+
|
131
|
+
def body
|
132
|
+
assert_type next_token, :OPEN_BRACE
|
133
|
+
body = AST::Body.new
|
134
|
+
while peek_token.type != :CLOSE_BRACE
|
135
|
+
body.statements.push statement
|
136
|
+
end
|
137
|
+
assert_type next_token, :CLOSE_BRACE
|
138
|
+
body
|
139
|
+
end
|
140
|
+
|
141
|
+
def bareword
|
142
|
+
assert_type next_token, :BAREWORD
|
143
|
+
AST::Variable.new name: token.val
|
144
|
+
end
|
145
|
+
|
146
|
+
def ternary(cond)
|
147
|
+
assert_type next_token, :QUESTION
|
148
|
+
ternary = AST::Ternary.new condition: cond
|
149
|
+
ternary.if_true = expression
|
150
|
+
assert_type next_token, :COLON
|
151
|
+
ternary.if_false = expression
|
152
|
+
ternary
|
153
|
+
end
|
154
|
+
|
155
|
+
def if
|
156
|
+
assert_type next_token, :IF
|
157
|
+
assert_type next_token, :OPEN_PAREN
|
158
|
+
node = AST::If.new condition: expression
|
159
|
+
assert_type next_token, :CLOSE_PAREN
|
160
|
+
node.then = statement
|
161
|
+
if try_peek_token && peek_token.type == :ELSE
|
162
|
+
assert_type next_token, :ELSE
|
163
|
+
node.else = statement
|
164
|
+
end
|
165
|
+
node
|
166
|
+
end
|
167
|
+
|
168
|
+
def for
|
169
|
+
assert_type next_token, :FOR
|
170
|
+
assert_type next_token, :OPEN_PAREN
|
171
|
+
# decide if this is a for(... in ...) or a for(;;) loop
|
172
|
+
stmt = statement(false)
|
173
|
+
assert_type next_token, :SEMICOLON, :CLOSE_PAREN
|
174
|
+
if token.type == :CLOSE_PAREN
|
175
|
+
# this is a for(... in ...) loop. we'll figure out how to deal with that in a sec
|
176
|
+
error! "for..in loops not implemented yet!" # @TODO
|
177
|
+
else
|
178
|
+
initializer = stmt
|
179
|
+
condition = statement(false)
|
180
|
+
assert_type next_token, :SEMICOLON
|
181
|
+
increment = statement(false)
|
182
|
+
assert_type next_token, :CLOSE_PAREN
|
183
|
+
AST::ForLoop.new initializer: initializer, condition: condition, increment: increment, body: statement
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def member_access(obj)
|
188
|
+
assert_type next_token, :MEMBER_ACCESS
|
189
|
+
assert_type next_token, :BAREWORD
|
190
|
+
access = AST::MemberAccess.new object: obj, member: token.val
|
191
|
+
if peek_token.type == :MEMBER_ACCESS
|
192
|
+
member_access access
|
193
|
+
elsif peek_token.type == :OPEN_PAREN
|
194
|
+
call access
|
195
|
+
elsif peek_token.type == :EQUALS
|
196
|
+
assignment access
|
197
|
+
else
|
198
|
+
access
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def call(callee)
|
203
|
+
assert_type next_token, :OPEN_PAREN
|
204
|
+
c = AST::Call.new callee: callee
|
205
|
+
while peek_token.type != :CLOSE_PAREN
|
206
|
+
c.arguments.push expression(true)
|
207
|
+
if peek_token.type == :COMMA
|
208
|
+
next_token
|
209
|
+
redo
|
210
|
+
end
|
211
|
+
end
|
212
|
+
next_token
|
213
|
+
c
|
214
|
+
end
|
215
|
+
|
216
|
+
def index(obj)
|
217
|
+
assert_type next_token, :OPEN_BRACKET
|
218
|
+
ind = expression
|
219
|
+
assert_type next_token, :CLOSE_BRACKET
|
220
|
+
AST::Index.new object: obj, index: ind
|
221
|
+
end
|
222
|
+
|
223
|
+
def return
|
224
|
+
assert_type next_token, :RETURN
|
225
|
+
AST::Return.new expression: expression
|
226
|
+
end
|
227
|
+
|
228
|
+
def var
|
229
|
+
assert_type next_token, :VAR
|
230
|
+
var_rest
|
231
|
+
end
|
232
|
+
|
233
|
+
def var_rest
|
234
|
+
assert_type next_token, :BAREWORD
|
235
|
+
decl = AST::Declaration.new(name: token.val)
|
236
|
+
return decl if peek_token.type == :SEMICOLON
|
237
|
+
|
238
|
+
assert_type next_token, :COMMA, :EQUALS
|
239
|
+
|
240
|
+
if token.type == :COMMA
|
241
|
+
AST::MultiExpression.new left: decl, right: var_rest
|
242
|
+
else
|
243
|
+
assignment = AST::Assignment.new left: decl, right: expression(true)
|
244
|
+
if peek_token.type == :SEMICOLON
|
245
|
+
assignment
|
246
|
+
elsif peek_token.type == :COMMA
|
247
|
+
next_token
|
248
|
+
AST::MultiExpression.new left: assignment, right: var_rest
|
249
|
+
else
|
250
|
+
error! "Unexpected #{peek_token.type}"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def assignment(lval)
|
256
|
+
assert_type next_token, :EQUALS
|
257
|
+
AST::Assignment.new left: lval, right: expression
|
258
|
+
end
|
259
|
+
|
260
|
+
def comma(left)
|
261
|
+
assert_type next_token, :COMMA
|
262
|
+
AST::MultiExpression.new left: left, right: expression
|
263
|
+
end
|
264
|
+
|
265
|
+
def number
|
266
|
+
assert_type next_token, :NUMBER
|
267
|
+
AST::Number.new number: token.val
|
268
|
+
end
|
269
|
+
|
270
|
+
def string
|
271
|
+
assert_type next_token, :STRING
|
272
|
+
AST::String.new string: token.val
|
273
|
+
end
|
274
|
+
|
275
|
+
def object_literal
|
276
|
+
assert_type next_token, :OPEN_BRACE
|
277
|
+
obj = AST::ObjectLiteral.new
|
278
|
+
while peek_token.type != :CLOSE_BRACE
|
279
|
+
assert_type next_token, :BAREWORD, :STRING, :NUMBER
|
280
|
+
key = token
|
281
|
+
assert_type next_token, :COLON
|
282
|
+
obj.items.push [key, expression(true)]
|
283
|
+
if peek_token.type == :COMMA
|
284
|
+
next_token
|
285
|
+
redo
|
286
|
+
end
|
287
|
+
end
|
288
|
+
next_token
|
289
|
+
obj
|
290
|
+
end
|
291
|
+
|
292
|
+
def array
|
293
|
+
assert_type next_token, :OPEN_BRACKET
|
294
|
+
ary = AST::Array.new
|
295
|
+
while peek_token.type != :CLOSE_BRACKET
|
296
|
+
ary.items.push expression(true)
|
297
|
+
if peek_token.type == :COMMA
|
298
|
+
next_token
|
299
|
+
redo
|
300
|
+
end
|
301
|
+
end
|
302
|
+
next_token
|
303
|
+
ary
|
304
|
+
end
|
305
|
+
|
306
|
+
def parens
|
307
|
+
assert_type next_token, :OPEN_PAREN
|
308
|
+
expr = expression
|
309
|
+
assert_type next_token, :CLOSE_PAREN
|
310
|
+
expr
|
311
|
+
end
|
312
|
+
|
313
|
+
def function
|
314
|
+
assert_type next_token, :FUNCTION
|
315
|
+
fn = AST::Function.new arguments: [], statements: []
|
316
|
+
error! unless [:BAREWORD, :OPEN_PAREN].include? next_token.type
|
317
|
+
if token.type == :BAREWORD
|
318
|
+
fn.name = token.val
|
319
|
+
assert_type next_token, :OPEN_PAREN
|
320
|
+
end
|
321
|
+
while peek_token.type == :BAREWORD
|
322
|
+
fn.arguments.push next_token.val
|
323
|
+
next_token if peek_token.type == :COMMA
|
324
|
+
end
|
325
|
+
assert_type next_token, :CLOSE_PAREN
|
326
|
+
assert_type next_token, :OPEN_BRACE
|
327
|
+
while peek_token.type != :CLOSE_BRACE
|
328
|
+
fn.statements.push statement
|
329
|
+
end
|
330
|
+
assert_type next_token, :CLOSE_BRACE
|
331
|
+
fn
|
332
|
+
end
|
333
|
+
|
334
|
+
def not
|
335
|
+
assert_type next_token, :NOT
|
336
|
+
AST::Not.new value: expression_after_unary
|
337
|
+
end
|
338
|
+
|
339
|
+
def tilde
|
340
|
+
assert_type next_token, :TILDE
|
341
|
+
AST::BinaryNot.new value: expression_after_unary
|
342
|
+
end
|
343
|
+
|
344
|
+
def unary_plus
|
345
|
+
assert_type next_token, :PLUS
|
346
|
+
AST::UnaryPlus.new value: expression_after_unary
|
347
|
+
end
|
348
|
+
|
349
|
+
def unary_minus
|
350
|
+
assert_type next_token, :MINUS
|
351
|
+
AST::Negation.new value: expression_after_unary
|
352
|
+
end
|
353
|
+
|
354
|
+
def post_increment(obj)
|
355
|
+
assert_type next_token, :INCREMENT
|
356
|
+
AST::PostIncrement.new value: obj
|
357
|
+
end
|
358
|
+
|
359
|
+
def post_decrement(obj)
|
360
|
+
assert_type next_token, :INCREMENT
|
361
|
+
AST::PostDecrement.new value: obj
|
362
|
+
end
|
363
|
+
|
364
|
+
def pre_increment(obj)
|
365
|
+
assert_type next_token, :INCREMENT
|
366
|
+
AST::PreIncrement.new value: obj
|
367
|
+
end
|
368
|
+
|
369
|
+
def pre_decrement(obj)
|
370
|
+
assert_type next_token, :INCREMENT
|
371
|
+
AST::PreDecrement.new value: obj
|
372
|
+
end
|
373
|
+
|
374
|
+
def typeof
|
375
|
+
assert_type next_token, :TYPEOF
|
376
|
+
AST::TypeOf.new value: expression_after_unary
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Twostroke
|
2
|
+
class Lexer
|
3
|
+
TOKENS = [
|
4
|
+
|
5
|
+
[ :MULTI_COMMENT, %r{/\*.*?\*/} ],
|
6
|
+
[ :SINGLE_COMMENT, /\/\/.*?$/ ],
|
7
|
+
|
8
|
+
[ :WHITESPACE, /\s+/ ],
|
9
|
+
[ :NUMBER, /\d+(\.\d*(e[+-]?\d+)?)?/, ->m { m[0].to_f } ],
|
10
|
+
|
11
|
+
*%w(function var if instanceof in else for while do this return throw typeof try catch).map do |w|
|
12
|
+
[ w.upcase.intern, /#{w}/ ]
|
13
|
+
end,
|
14
|
+
[ :BAREWORD, /[a-zA-Z_][a-zA-Z_0-9]*/, ->m { m[0] } ],
|
15
|
+
|
16
|
+
[ :STRING, /(["'])((\\.|[^\1])*?[^\1\\]?)\1/, ->m do
|
17
|
+
m[2].gsub(/\\([bfnrt])/) { |m|
|
18
|
+
case m[1]
|
19
|
+
when "b"; "\b"
|
20
|
+
when "n"; "\n"
|
21
|
+
when "f"; "\f"
|
22
|
+
when "r"; "\r"
|
23
|
+
when "t"; "\t"
|
24
|
+
end
|
25
|
+
}
|
26
|
+
.gsub(/\\([0-6]{1,3})/) { |m| m[1].to_i(7).chr }
|
27
|
+
.gsub(/\\x([a-f0-9]{2})/i) { |m| m[1].to_i(16).chr }
|
28
|
+
.gsub(/\\u([a-f0-9]{4})/i) { |m| m[1].to_i(16).chr }
|
29
|
+
.gsub(/\\(.)/) { |m| m[1] }
|
30
|
+
end ],
|
31
|
+
|
32
|
+
[ :OPEN_PAREN, /\(/ ],
|
33
|
+
[ :CLOSE_PAREN, /\)/ ],
|
34
|
+
[ :OPEN_BRACKET, /\[/ ],
|
35
|
+
[ :CLOSE_BRACKET, /\]/ ],
|
36
|
+
[ :OPEN_BRACE, /\{/ ],
|
37
|
+
[ :CLOSE_BRACE, /\}/ ],
|
38
|
+
|
39
|
+
[ :MEMBER_ACCESS, /\./ ],
|
40
|
+
|
41
|
+
[ :INCREMENT, /\+\+/ ],
|
42
|
+
[ :DECREMENT, /--/ ],
|
43
|
+
[ :PLUS, /\+/ ],
|
44
|
+
[ :MINUS, /-/ ],
|
45
|
+
[ :ASTERISK, /\*/ ],
|
46
|
+
[ :SLASH, /\// ],
|
47
|
+
[ :MOD, /%/ ],
|
48
|
+
[ :QUESTION, /\?/ ],
|
49
|
+
[ :COMMA, /,/ ],
|
50
|
+
[ :SEMICOLON, /;/ ],
|
51
|
+
[ :COLON, /:/ ],
|
52
|
+
|
53
|
+
[ :AND, /&&/ ],
|
54
|
+
[ :AMPERSAND, /&/ ],
|
55
|
+
[ :OR, /\|\|/ ],
|
56
|
+
[ :PIPE, /\|/ ],
|
57
|
+
[ :TRIPLE_EQUALS, /===/ ],
|
58
|
+
[ :DOUBLE_EQUALS, /==/ ],
|
59
|
+
[ :EQUALS, /=/ ],
|
60
|
+
[ :NOT_DOUBLE_EQUALS, /!==/ ],
|
61
|
+
[ :NOT_EQUALS, /!=/ ],
|
62
|
+
[ :NOT, /!/ ],
|
63
|
+
[ :TILDE, /~/ ],
|
64
|
+
[ :CARET, /\^/ ],
|
65
|
+
|
66
|
+
[ :LEFT_SHIFT, /<</ ],
|
67
|
+
[ :RIGHT_TRIPLE_SHIFT, />>>/ ],
|
68
|
+
[ :RIGHT_SHIFT, />>/ ],
|
69
|
+
[ :LTE, /<=/ ],
|
70
|
+
[ :GTE, />=/ ],
|
71
|
+
[ :LT, /</ ],
|
72
|
+
[ :GT, />/ ],
|
73
|
+
|
74
|
+
].map do |a|
|
75
|
+
[a[0], Regexp.new("\\A#{a[1].source}", Regexp::MULTILINE), a[2]]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/twostroke.rb
ADDED
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: twostroke
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Charlie Somerville
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-04 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: An implementation of Javascript written in pure Ruby. This project currently
|
15
|
+
includes a WIP parser, and will include a runtime.
|
16
|
+
email:
|
17
|
+
- charlie@charliesomerville.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- lib/twostroke/ast/array.rb
|
23
|
+
- lib/twostroke/ast/assignment.rb
|
24
|
+
- lib/twostroke/ast/binary_operators.rb
|
25
|
+
- lib/twostroke/ast/body.rb
|
26
|
+
- lib/twostroke/ast/call.rb
|
27
|
+
- lib/twostroke/ast/declaration.rb
|
28
|
+
- lib/twostroke/ast/for_loop.rb
|
29
|
+
- lib/twostroke/ast/function.rb
|
30
|
+
- lib/twostroke/ast/if.rb
|
31
|
+
- lib/twostroke/ast/index.rb
|
32
|
+
- lib/twostroke/ast/member_access.rb
|
33
|
+
- lib/twostroke/ast/multi_expression.rb
|
34
|
+
- lib/twostroke/ast/number.rb
|
35
|
+
- lib/twostroke/ast/object_literal.rb
|
36
|
+
- lib/twostroke/ast/return.rb
|
37
|
+
- lib/twostroke/ast/string.rb
|
38
|
+
- lib/twostroke/ast/ternary.rb
|
39
|
+
- lib/twostroke/ast/unary_operators.rb
|
40
|
+
- lib/twostroke/ast/unsorted_binop.rb
|
41
|
+
- lib/twostroke/ast/variable.rb
|
42
|
+
- lib/twostroke/ast.rb
|
43
|
+
- lib/twostroke/error.rb
|
44
|
+
- lib/twostroke/lexer.rb
|
45
|
+
- lib/twostroke/parser.rb
|
46
|
+
- lib/twostroke/tokens.rb
|
47
|
+
- lib/twostroke.rb
|
48
|
+
homepage: http://github.com/charliesome/twostroke
|
49
|
+
licenses: []
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.8.10
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: A Ruby implementation of Javascript
|
72
|
+
test_files: []
|