syntax_tree 5.0.0 → 5.1.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,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