-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcompiler.rb
177 lines (152 loc) · 3.56 KB
/
compiler.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
class Tokenizer
TOKEN_TYPES = [
[:def, /\bmake\b/],
[:end, /\bend\b/],
[:identifier,/\b[a-zA-Z]+\b/],
[:integer,/\b[0-9]+\b/],
[:oparen,/\(/],
[:cparen,/\)/],
[:comma,/,/],
]
def initialize(code)
@code = code
end
def tokenize
tokens = []
until @code.empty?
tokens << tokenize_one_token
@code = @code.strip
end
tokens
end
def tokenize_one_token
TOKEN_TYPES.each do |type, re|
re = /\A(#{re})/
if @code =~ re
value = $1
@code = @code[value.length..-1]
return Token.new(type,value)
end
end
raise RuntimeError.new(
"Unexpected character #{@code.inspect}"
)
end
end
class Parser
def initialize(tokens)
@tokens = tokens
end
def parse
parse_fn
end
def parse_fn
consume(:def)
name = consume(:identifier).value
arg_names = parse_arg_name
body = parse_expr
consume(:end)
DefNode.new(name, arg_names, body)
end
def parse_expr
if peek(:integer)
parse_integer
elsif peek(:identifier) && peek(:oparen, 1)
parse_call
else
parse_var_ref
end
end
def parse_integer
IntegerNode.new(consume(:integer).value.to_i)
end
def parse_call
name = consume(:identifier).value
arg_exprs = parse_arg_exprs
CallNode.new(name, arg_exprs)
end
def parse_arg_name
arg_names=[]
consume(:oparen)
if peek(:identifier)
arg_names << consume(:identifier).value
while peek(:comma)
consume(:comma)
arg_names << consume(:identifier).value
end
end
consume(:cparen)
arg_names
end
def parse_var_ref
VarRefNode.new(consume(:identifier).value)
end
def parse_arg_exprs
arg_exprs = []
consume(:oparen)
unless peek(:cparen)
arg_exprs << parse_expr
while peek(:comma)
consume(:comma)
arg_exprs << parse_expr
end
end
consume(:cparen)
arg_exprs
end
def consume(expected_type)
token = @tokens.shift
if token.type == expected_type
token
else
raise RuntimeError.new(
"Expected token type #{expected_type.inspect} but got #{token.type.inspect}"
)
end
end
def peek(expected_type, offset=0)
@tokens.fetch(offset).type == expected_type
end
end
class Generator
def generate(node)
case node
when DefNode
"function #{node.name}(#{node.arg_names.join(",")}){\n\treturn #{generate(node.body)}\n}"
when IntegerNode
node.value
when CallNode
"#{node.name}(#{node.arg_exprs.map { |expr| generate(expr) }.join(",")})"
when VarRefNode
node.value
else
raise RuntimeError.new("Unexpected node type: #{node.class}")
end
end
end
DefNode = Struct.new(:name, :arg_names, :body)
IntegerNode = Struct.new(:value)
CallNode = Struct.new(:name, :arg_exprs)
VarRefNode = Struct.new(:value)
Token = Struct.new(:type, :value)
#Recieving File Name from User
puts "Enter the file name"
file_name = gets.chomp.to_s
# Checking if the file exists
unless File.exist?(file_name)
raise RuntimeError.new("#{file_name} not found")
end
# Tokenizing the code
tokens = Tokenizer.new(File.read(file_name)).tokenize
# Parsing the token tree
tree = Parser.new(tokens).parse
# Generating the code
generated_code = Generator.new.generate(tree)
# Adding runtime and test code
RUNTIME = "function add(x,y){ return x + y};"
TEST = "console.log(f(1,2));"
# Outputting the final code in JS to a file.
File.open('output.js', 'w') do |file|
file.puts [RUNTIME,generated_code,TEST].join("\n")
end
puts "Code has been generated in output.js"