syntax_tree 5.0.1 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module YARV
5
+ # Parses the given source code into a syntax tree, compiles that syntax tree
6
+ # into YARV bytecode.
7
+ class Bf
8
+ attr_reader :source
9
+
10
+ def initialize(source)
11
+ @source = source
12
+ end
13
+
14
+ def compile
15
+ # Set up the top-level instruction sequence that will be returned.
16
+ iseq = InstructionSequence.new(:top, "<compiled>", nil, location)
17
+
18
+ # Set up the $tape global variable that will hold our state.
19
+ iseq.duphash({ 0 => 0 })
20
+ iseq.setglobal(:$tape)
21
+ iseq.getglobal(:$tape)
22
+ iseq.putobject(0)
23
+ iseq.send(YARV.calldata(:default=, 1))
24
+
25
+ # Set up the $cursor global variable that will hold the current position
26
+ # in the tape.
27
+ iseq.putobject(0)
28
+ iseq.setglobal(:$cursor)
29
+
30
+ stack = []
31
+ source
32
+ .each_char
33
+ .chunk do |char|
34
+ # For each character, we're going to assign a type to it. This
35
+ # allows a couple of optimizations to be made by combining multiple
36
+ # instructions into single instructions, e.g., +++ becomes a single
37
+ # change_by(3) instruction.
38
+ case char
39
+ when "+", "-"
40
+ :change
41
+ when ">", "<"
42
+ :shift
43
+ when "."
44
+ :output
45
+ when ","
46
+ :input
47
+ when "[", "]"
48
+ :loop
49
+ else
50
+ :ignored
51
+ end
52
+ end
53
+ .each do |type, chunk|
54
+ # For each chunk, we're going to emit the appropriate instruction.
55
+ case type
56
+ when :change
57
+ change_by(iseq, chunk.count("+") - chunk.count("-"))
58
+ when :shift
59
+ shift_by(iseq, chunk.count(">") - chunk.count("<"))
60
+ when :output
61
+ chunk.length.times { output_char(iseq) }
62
+ when :input
63
+ chunk.length.times { input_char(iseq) }
64
+ when :loop
65
+ chunk.each do |char|
66
+ case char
67
+ when "["
68
+ stack << loop_start(iseq)
69
+ when "]"
70
+ loop_end(iseq, *stack.pop)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ iseq.leave
77
+ iseq.compile!
78
+ iseq
79
+ end
80
+
81
+ private
82
+
83
+ # This is the location of the top instruction sequence, derived from the
84
+ # source string.
85
+ def location
86
+ Location.new(
87
+ start_line: 1,
88
+ start_char: 0,
89
+ start_column: 0,
90
+ end_line: source.count("\n") + 1,
91
+ end_char: source.size,
92
+ end_column: source.size - (source.rindex("\n") || 0) - 1
93
+ )
94
+ end
95
+
96
+ # $tape[$cursor] += value
97
+ def change_by(iseq, value)
98
+ iseq.getglobal(:$tape)
99
+ iseq.getglobal(:$cursor)
100
+
101
+ iseq.getglobal(:$tape)
102
+ iseq.getglobal(:$cursor)
103
+ iseq.send(YARV.calldata(:[], 1))
104
+
105
+ if value < 0
106
+ iseq.putobject(-value)
107
+ iseq.send(YARV.calldata(:-, 1))
108
+ else
109
+ iseq.putobject(value)
110
+ iseq.send(YARV.calldata(:+, 1))
111
+ end
112
+
113
+ iseq.send(YARV.calldata(:[]=, 2))
114
+ end
115
+
116
+ # $cursor += value
117
+ def shift_by(iseq, value)
118
+ iseq.getglobal(:$cursor)
119
+
120
+ if value < 0
121
+ iseq.putobject(-value)
122
+ iseq.send(YARV.calldata(:-, 1))
123
+ else
124
+ iseq.putobject(value)
125
+ iseq.send(YARV.calldata(:+, 1))
126
+ end
127
+
128
+ iseq.setglobal(:$cursor)
129
+ end
130
+
131
+ # $stdout.putc($tape[$cursor].chr)
132
+ def output_char(iseq)
133
+ iseq.getglobal(:$stdout)
134
+
135
+ iseq.getglobal(:$tape)
136
+ iseq.getglobal(:$cursor)
137
+ iseq.send(YARV.calldata(:[], 1))
138
+ iseq.send(YARV.calldata(:chr))
139
+
140
+ iseq.send(YARV.calldata(:putc, 1))
141
+ end
142
+
143
+ # $tape[$cursor] = $stdin.getc.ord
144
+ def input_char(iseq)
145
+ iseq.getglobal(:$tape)
146
+ iseq.getglobal(:$cursor)
147
+
148
+ iseq.getglobal(:$stdin)
149
+ iseq.send(YARV.calldata(:getc))
150
+ iseq.send(YARV.calldata(:ord))
151
+
152
+ iseq.send(YARV.calldata(:[]=, 2))
153
+ end
154
+
155
+ # unless $tape[$cursor] == 0
156
+ def loop_start(iseq)
157
+ start_label = iseq.label
158
+ end_label = iseq.label
159
+
160
+ iseq.push(start_label)
161
+ iseq.getglobal(:$tape)
162
+ iseq.getglobal(:$cursor)
163
+ iseq.send(YARV.calldata(:[], 1))
164
+
165
+ iseq.putobject(0)
166
+ iseq.send(YARV.calldata(:==, 1))
167
+ iseq.branchunless(end_label)
168
+
169
+ [start_label, end_label]
170
+ end
171
+
172
+ # Jump back to the start of the loop.
173
+ def loop_end(iseq, start_label, end_label)
174
+ iseq.jump(start_label)
175
+ iseq.push(end_label)
176
+ end
177
+ end
178
+ end
179
+ end