syntax_tree 5.0.1 → 5.2.0

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.
@@ -0,0 +1,176 @@
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("<compiled>", "<compiled>", 1, :top)
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
+ # $tape[$cursor] += value
84
+ def change_by(iseq, value)
85
+ iseq.getglobal(:$tape)
86
+ iseq.getglobal(:$cursor)
87
+
88
+ iseq.getglobal(:$tape)
89
+ iseq.getglobal(:$cursor)
90
+ iseq.send(YARV.calldata(:[], 1))
91
+
92
+ if value < 0
93
+ iseq.putobject(-value)
94
+ iseq.send(YARV.calldata(:-, 1))
95
+ else
96
+ iseq.putobject(value)
97
+ iseq.send(YARV.calldata(:+, 1))
98
+ end
99
+
100
+ iseq.send(YARV.calldata(:[]=, 2))
101
+ iseq.pop
102
+ end
103
+
104
+ # $cursor += value
105
+ def shift_by(iseq, value)
106
+ iseq.getglobal(:$cursor)
107
+
108
+ if value < 0
109
+ iseq.putobject(-value)
110
+ iseq.send(YARV.calldata(:-, 1))
111
+ else
112
+ iseq.putobject(value)
113
+ iseq.send(YARV.calldata(:+, 1))
114
+ end
115
+
116
+ iseq.setglobal(:$cursor)
117
+ end
118
+
119
+ # $stdout.putc($tape[$cursor].chr)
120
+ def output_char(iseq)
121
+ iseq.getglobal(:$stdout)
122
+
123
+ iseq.getglobal(:$tape)
124
+ iseq.getglobal(:$cursor)
125
+ iseq.send(YARV.calldata(:[], 1))
126
+ iseq.send(YARV.calldata(:chr))
127
+
128
+ iseq.send(YARV.calldata(:putc, 1))
129
+ iseq.pop
130
+ end
131
+
132
+ # $tape[$cursor] = $stdin.getc.ord
133
+ def input_char(iseq)
134
+ iseq.getglobal(:$tape)
135
+ iseq.getglobal(:$cursor)
136
+
137
+ iseq.getglobal(:$stdin)
138
+ iseq.send(YARV.calldata(:getc))
139
+ iseq.send(YARV.calldata(:ord))
140
+
141
+ iseq.send(YARV.calldata(:[]=, 2))
142
+ iseq.pop
143
+ end
144
+
145
+ # unless $tape[$cursor] == 0
146
+ def loop_start(iseq)
147
+ start_label = iseq.label
148
+ end_label = iseq.label
149
+
150
+ iseq.push(start_label)
151
+ iseq.getglobal(:$tape)
152
+ iseq.getglobal(:$cursor)
153
+ iseq.send(YARV.calldata(:[], 1))
154
+
155
+ iseq.putobject(0)
156
+ iseq.send(YARV.calldata(:==, 1))
157
+ iseq.branchif(end_label)
158
+
159
+ [start_label, end_label]
160
+ end
161
+
162
+ # Jump back to the start of the loop.
163
+ def loop_end(iseq, start_label, end_label)
164
+ iseq.getglobal(:$tape)
165
+ iseq.getglobal(:$cursor)
166
+ iseq.send(YARV.calldata(:[], 1))
167
+
168
+ iseq.putobject(0)
169
+ iseq.send(YARV.calldata(:==, 1))
170
+ iseq.branchunless(start_label)
171
+
172
+ iseq.push(end_label)
173
+ end
174
+ end
175
+ end
176
+ end