blockly_interpreter 0.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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +12 -0
- data/COPYING +8 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +91 -0
- data/Guardfile +22 -0
- data/README.md +72 -0
- data/Rakefile +10 -0
- data/app/assets/javascripts/blockly_interpreter/extension_blocks.js +1 -0
- data/app/assets/javascripts/blockly_interpreter/extension_blocks/dates.js +16 -0
- data/app/assets/javascripts/blockly_interpreter/extension_blocks/debugging.js +17 -0
- data/app/assets/javascripts/blockly_interpreter/extension_blocks/lists.js +45 -0
- data/app/assets/javascripts/blockly_interpreter/extension_blocks/logic.js +274 -0
- data/app/assets/javascripts/blockly_interpreter/extension_blocks/text.js.erb +21 -0
- data/bin/_guard-core +16 -0
- data/bin/console +14 -0
- data/bin/guard +16 -0
- data/bin/rake +16 -0
- data/bin/setup +7 -0
- data/blockly_interpreter.gemspec +33 -0
- data/exe/blocklyi +13 -0
- data/exe/rublocklyc +31 -0
- data/lib/blockly_interpreter.rb +25 -0
- data/lib/blockly_interpreter/block.rb +113 -0
- data/lib/blockly_interpreter/block_library.rb +14 -0
- data/lib/blockly_interpreter/console_interpreter.rb +15 -0
- data/lib/blockly_interpreter/core_blocks.rb +55 -0
- data/lib/blockly_interpreter/core_blocks/arithmetic_operator_block.rb +24 -0
- data/lib/blockly_interpreter/core_blocks/boolean_block.rb +23 -0
- data/lib/blockly_interpreter/core_blocks/comparison_operator_block.rb +50 -0
- data/lib/blockly_interpreter/core_blocks/for_block.rb +68 -0
- data/lib/blockly_interpreter/core_blocks/for_each_block.rb +42 -0
- data/lib/blockly_interpreter/core_blocks/get_variable_block.rb +19 -0
- data/lib/blockly_interpreter/core_blocks/if_block.rb +105 -0
- data/lib/blockly_interpreter/core_blocks/lists_create_empty_block.rb +17 -0
- data/lib/blockly_interpreter/core_blocks/lists_create_with_block.rb +48 -0
- data/lib/blockly_interpreter/core_blocks/lists_get_index_block.rb +95 -0
- data/lib/blockly_interpreter/core_blocks/logic_negate_block.rb +20 -0
- data/lib/blockly_interpreter/core_blocks/logical_operator_block.rb +22 -0
- data/lib/blockly_interpreter/core_blocks/number_block.rb +23 -0
- data/lib/blockly_interpreter/core_blocks/procedure_block.rb +49 -0
- data/lib/blockly_interpreter/core_blocks/procedures_call_no_return_block.rb +21 -0
- data/lib/blockly_interpreter/core_blocks/procedures_call_return_block.rb +21 -0
- data/lib/blockly_interpreter/core_blocks/procedures_def_no_return_block.rb +31 -0
- data/lib/blockly_interpreter/core_blocks/procedures_def_return_block.rb +64 -0
- data/lib/blockly_interpreter/core_blocks/procedures_if_return_block.rb +45 -0
- data/lib/blockly_interpreter/core_blocks/repeat_times_block.rb +33 -0
- data/lib/blockly_interpreter/core_blocks/set_variable_block.rb +24 -0
- data/lib/blockly_interpreter/core_blocks/text_block.rb +23 -0
- data/lib/blockly_interpreter/core_blocks/text_change_case_block.rb +32 -0
- data/lib/blockly_interpreter/core_blocks/text_join_block.rb +50 -0
- data/lib/blockly_interpreter/dsl.rb +291 -0
- data/lib/blockly_interpreter/dsl_generator.rb +147 -0
- data/lib/blockly_interpreter/engine.rb +8 -0
- data/lib/blockly_interpreter/execution_context.rb +72 -0
- data/lib/blockly_interpreter/extension_blocks.rb +25 -0
- data/lib/blockly_interpreter/extension_blocks/date_today_block.rb +17 -0
- data/lib/blockly_interpreter/extension_blocks/debug_message_block.rb +18 -0
- data/lib/blockly_interpreter/extension_blocks/lists_append_block.rb +34 -0
- data/lib/blockly_interpreter/extension_blocks/lists_concat_block.rb +37 -0
- data/lib/blockly_interpreter/extension_blocks/lists_include_operator_block.rb +52 -0
- data/lib/blockly_interpreter/extension_blocks/object_present_block.rb +21 -0
- data/lib/blockly_interpreter/extension_blocks/switch_block.rb +107 -0
- data/lib/blockly_interpreter/extension_blocks/text_inflect_block.rb +27 -0
- data/lib/blockly_interpreter/generic_block_dsl_generator.rb +64 -0
- data/lib/blockly_interpreter/interpreter.rb +25 -0
- data/lib/blockly_interpreter/parser.rb +117 -0
- data/lib/blockly_interpreter/program.rb +51 -0
- data/lib/blockly_interpreter/program_cache.rb +19 -0
- data/lib/blockly_interpreter/test_helper.rb +98 -0
- data/lib/blockly_interpreter/version.rb +3 -0
- metadata +272 -0
@@ -0,0 +1,291 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module BlocklyInterpreter::DSL
|
4
|
+
class BlockContext
|
5
|
+
attr_reader :blocks, :procedure_blocks
|
6
|
+
|
7
|
+
def self.register_block_class(block_class)
|
8
|
+
if block_class.const_defined?(:DSLMethods)
|
9
|
+
self.include block_class.const_get(:DSLMethods)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@blocks = []
|
15
|
+
@procedure_blocks = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def block(block_type, &proc)
|
19
|
+
@blocks << BlockBuilder.new(block_type).tap do |builder|
|
20
|
+
builder.instance_exec(&proc) if proc
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_comment(comment, pinned = false, &proc)
|
25
|
+
instance_exec(&proc)
|
26
|
+
@blocks.last.tap { |block| block.set_comment(comment, pinned) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def shadow(&proc)
|
30
|
+
instance_exec(&proc)
|
31
|
+
@blocks.last.tap { |block| block.shadow! }
|
32
|
+
end
|
33
|
+
|
34
|
+
def with_position(x, y, &proc)
|
35
|
+
instance_exec(&proc)
|
36
|
+
@blocks.last.tap { |block| block.set_position!(x, y) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def procedures(&proc)
|
40
|
+
procedures_context = BlockContext.new.tap do |builder|
|
41
|
+
builder.instance_exec(&proc) if proc
|
42
|
+
end
|
43
|
+
|
44
|
+
@procedure_blocks.push(*procedures_context.blocks)
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_xml(doc, block_index = 0)
|
48
|
+
return unless blocks.any?
|
49
|
+
|
50
|
+
block = blocks[block_index]
|
51
|
+
Nokogiri::XML::Node.new(block.tag_name, doc).tap do |node|
|
52
|
+
blocks[block_index].to_xml(node)
|
53
|
+
|
54
|
+
if blocks.size > block_index + 1
|
55
|
+
next_node = Nokogiri::XML::Node.new('next', doc)
|
56
|
+
next_node.add_child to_xml(doc, block_index + 1)
|
57
|
+
node.add_child(next_node)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class BlockBuilder
|
64
|
+
attr_reader :block_type, :values, :statements, :fields, :mutation_attrs, :mutation_child_procs, :comment, :comment_pinned, :is_shadow, :x, :y
|
65
|
+
|
66
|
+
def initialize(block_type)
|
67
|
+
@block_type = block_type
|
68
|
+
@values = {}
|
69
|
+
@statements = {}
|
70
|
+
@fields = {}
|
71
|
+
@mutation_attrs = {}
|
72
|
+
@mutation_child_procs = []
|
73
|
+
@comment = nil
|
74
|
+
@comment_pinned = nil
|
75
|
+
@is_shadow = false
|
76
|
+
@x = nil
|
77
|
+
@y = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_xml(node)
|
81
|
+
node['type'] = block_type
|
82
|
+
document = node.document
|
83
|
+
|
84
|
+
node['x'] = x if x
|
85
|
+
node['y'] = y if y
|
86
|
+
|
87
|
+
node.add_child(mutations_to_xml(document)) if mutation_attrs.any? || mutation_child_procs.any?
|
88
|
+
node.add_child(comment_to_xml(document)) if comment.present?
|
89
|
+
|
90
|
+
values.each do |name, context|
|
91
|
+
node.add_child subblock_to_xml('value', name, context, document)
|
92
|
+
end
|
93
|
+
|
94
|
+
statements.each do |name, context|
|
95
|
+
node.add_child subblock_to_xml('statement', name, context, document)
|
96
|
+
end
|
97
|
+
|
98
|
+
fields.each do |name, value|
|
99
|
+
node.add_child field_to_xml(name, value, document)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def subblock_to_xml(tag_name, subblock_name, context, doc)
|
104
|
+
Nokogiri::XML::Node.new(tag_name, doc).tap do |node|
|
105
|
+
node['name'] = subblock_name
|
106
|
+
node.add_child context.to_xml(doc)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def mutations_to_xml(document)
|
111
|
+
Nokogiri::XML::Node.new('mutation', document).tap do |mutation_node|
|
112
|
+
mutation_attrs.each do |name, value|
|
113
|
+
mutation_node[name] = value.to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
mutation_child_procs.each do |(tag_name, proc)|
|
117
|
+
child = Nokogiri::XML::Node.new(tag_name.to_s, document)
|
118
|
+
proc.call(child)
|
119
|
+
mutation_node.add_child child
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def field_to_xml(name, value, document)
|
125
|
+
Nokogiri::XML::Node.new('field', document).tap do |field_node|
|
126
|
+
field_node['name'] = name
|
127
|
+
field_node.add_child Nokogiri::XML::Text.new(value.to_s, document)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def comment_to_xml(document)
|
132
|
+
Nokogiri::XML::Node.new('comment', document).tap do |comment_node|
|
133
|
+
comment_node.content = comment
|
134
|
+
comment_node['pinned'] = comment_pinned.inspect unless comment_pinned.nil?
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def build_subblock(&proc)
|
139
|
+
BlockContext.new.tap do |builder|
|
140
|
+
builder.instance_exec(&proc)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def value(name, &proc)
|
145
|
+
@values[name.to_s] = build_subblock(&proc)
|
146
|
+
end
|
147
|
+
|
148
|
+
def statement(name, &proc)
|
149
|
+
@statements[name.to_s] = build_subblock(&proc)
|
150
|
+
end
|
151
|
+
|
152
|
+
def field(name, value)
|
153
|
+
@fields[name.to_s] = value
|
154
|
+
end
|
155
|
+
|
156
|
+
def mutation_attr(name, value)
|
157
|
+
@mutation_attrs[name.to_s] = value
|
158
|
+
end
|
159
|
+
|
160
|
+
def mutation_child(tag_name, &proc)
|
161
|
+
@mutation_child_procs << [tag_name, proc]
|
162
|
+
end
|
163
|
+
|
164
|
+
def set_comment(comment, pinned = false)
|
165
|
+
@comment = comment
|
166
|
+
@comment_pinned = pinned
|
167
|
+
end
|
168
|
+
|
169
|
+
def set_position!(x, y)
|
170
|
+
@x = x
|
171
|
+
@y = y
|
172
|
+
end
|
173
|
+
|
174
|
+
def tag_name
|
175
|
+
if is_shadow
|
176
|
+
'shadow'
|
177
|
+
else
|
178
|
+
'block'
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def shadow!
|
183
|
+
@is_shadow = true
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class BinaryOperationBlockBuilder < BlockBuilder
|
188
|
+
def initialize(block_type, op, a = nil, b = nil)
|
189
|
+
super block_type
|
190
|
+
field :OP, op
|
191
|
+
|
192
|
+
a(a) unless a.nil?
|
193
|
+
b(b) unless b.nil?
|
194
|
+
end
|
195
|
+
|
196
|
+
def cast_static_value_proc(static_value)
|
197
|
+
case static_value
|
198
|
+
when Numeric then -> { math_number(static_value) }
|
199
|
+
when String then -> { text(static_value) }
|
200
|
+
when true, false then -> { logic_boolean(static_value) }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def value_with_static_option(name, static_value = nil, &proc)
|
205
|
+
proc ||= cast_static_value_proc(static_value)
|
206
|
+
value name, &proc
|
207
|
+
end
|
208
|
+
|
209
|
+
def a(static_value = nil, &proc)
|
210
|
+
value_with_static_option :A, static_value, &proc
|
211
|
+
end
|
212
|
+
|
213
|
+
def b(static_value = nil, &proc)
|
214
|
+
value_with_static_option :B, static_value, &proc
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
class BinaryOperationDSLGenerator
|
219
|
+
include BlocklyInterpreter::DSLGenerator
|
220
|
+
|
221
|
+
attr_reader :block, :dsl_method_name
|
222
|
+
|
223
|
+
def initialize(block, dsl_method_name)
|
224
|
+
@block = block
|
225
|
+
@dsl_method_name = dsl_method_name
|
226
|
+
end
|
227
|
+
|
228
|
+
def castable_static_value(value)
|
229
|
+
case value
|
230
|
+
when BlocklyInterpreter::CoreBlocks::NumberBlock then value.fields['NUM'].to_i
|
231
|
+
when BlocklyInterpreter::CoreBlocks::BooleanBlock then value.to_bool
|
232
|
+
when BlocklyInterpreter::CoreBlocks::TextBlock then value.fields['TEXT']
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def cast_operands
|
237
|
+
@cast_operands ||= begin
|
238
|
+
a, b = ['A', 'B'].map { |value_name| block.values[value_name] }
|
239
|
+
[a, b].map { |value| castable_static_value value }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def value_block(value_name, cast_operand)
|
244
|
+
return if cast_operand
|
245
|
+
method_call_with_block_or_nothing(value_name.downcase, '', block.values[value_name])
|
246
|
+
end
|
247
|
+
|
248
|
+
def block_contents
|
249
|
+
cast_a, cast_b = cast_operands
|
250
|
+
[
|
251
|
+
value_block('A', cast_a),
|
252
|
+
value_block('B', cast_b)
|
253
|
+
].compact
|
254
|
+
end
|
255
|
+
|
256
|
+
def method_args
|
257
|
+
args = [block.fields['OP'].to_sym.inspect]
|
258
|
+
cast_a, cast_b = cast_operands
|
259
|
+
|
260
|
+
args << cast_a.inspect if cast_a || cast_b
|
261
|
+
args << cast_b.inspect if cast_b
|
262
|
+
|
263
|
+
args
|
264
|
+
end
|
265
|
+
|
266
|
+
def dsl
|
267
|
+
method_call_with_possible_block(dsl_method_name, method_args.join(', '), block_contents)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def self.build_xml(&block)
|
272
|
+
doc = Nokogiri::XML::Document.new
|
273
|
+
root = Nokogiri::XML::Node.new('xml', doc)
|
274
|
+
root['xmlns'] = 'http://www.w3.org/1999/xhtml'
|
275
|
+
doc.add_child root
|
276
|
+
|
277
|
+
context = BlockContext.new
|
278
|
+
context.instance_exec(&block)
|
279
|
+
first_block = context.to_xml(doc)
|
280
|
+
root.add_child(first_block) if first_block
|
281
|
+
|
282
|
+
context.procedure_blocks.each do |proc|
|
283
|
+
Nokogiri::XML::Node.new(proc.tag_name, doc).tap do |node|
|
284
|
+
proc.to_xml(node)
|
285
|
+
root.add_child(node)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
doc.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION | Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS)
|
290
|
+
end
|
291
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module BlocklyInterpreter::DSLGenerator
|
2
|
+
# Calls #flatten on an enumerable object recursively. Any flattenable things inside obj will also be flattened.
|
3
|
+
def deep_flatten(obj)
|
4
|
+
if obj.respond_to?(:flatten)
|
5
|
+
obj.flatten.map { |item| deep_flatten(item) }
|
6
|
+
else
|
7
|
+
obj
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Given a piece of code as a string, removes any trailing whitespace from each line.
|
12
|
+
def strip_trailing_whitespace(code)
|
13
|
+
code.gsub(/[ \t]+$/, '')
|
14
|
+
end
|
15
|
+
|
16
|
+
# Given a piece of code, indents it by `level` spaces.
|
17
|
+
#
|
18
|
+
# indent can accept code as a string (obviously), but can also accept a BlocklyInterpreter::Block, which will
|
19
|
+
# automatically be converted to a DSL string and then indented. It can also accept Arrays consisting of strings,
|
20
|
+
# BlocklyInterpreter::Blocks, other arrays, and nil, which will be concatenated and indented.
|
21
|
+
def indent(code, level = 2)
|
22
|
+
lines = case code
|
23
|
+
when String then code.split("\n")
|
24
|
+
when BlocklyInterpreter::Block then start_block_to_dsl(code).split("\n")
|
25
|
+
when Array then deep_flatten(code).compact.flat_map { |code| code.split("\n") }
|
26
|
+
else code
|
27
|
+
end
|
28
|
+
|
29
|
+
lines.map { |line| (" " * level) + line }.join("\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
# Generates a Ruby method call given a method name and an arguments string. If `parens` is true, the arguments
|
33
|
+
# will be wrapped in parentheses; otherwise, the parentheses will be omitted.
|
34
|
+
def method_call(method_name, args, parens = false)
|
35
|
+
code = if parens
|
36
|
+
method_name.dup.tap do |ruby|
|
37
|
+
ruby << "(#{args})" if args.present?
|
38
|
+
end
|
39
|
+
else
|
40
|
+
[method_name, args].map(&:presence).compact.join(" ")
|
41
|
+
end
|
42
|
+
|
43
|
+
strip_trailing_whitespace(code)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Generates a Ruby method call with a block. If `block_contents` is missing, generates the method call with the
|
47
|
+
# block omitted. See `method_call_with_block_or_nothing` for argument descriptions.
|
48
|
+
def method_call_with_possible_block(method_name, args, block_contents, *block_arg_names)
|
49
|
+
method_call_with_block_or_nothing(method_name, args, block_contents, *block_arg_names) || method_call(method_name, args)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Generates a Ruby method call with a block. If `block_contents` is missing, this method will return nil.
|
53
|
+
#
|
54
|
+
# Arguments:
|
55
|
+
# * `method_name` - the name of the method, as a string
|
56
|
+
# * `args` - an arguments string for the method call, or nil if there are no arguments
|
57
|
+
# * `block_contents` - an indentable object (i.e. a string, BlocklyInterpreter::Block, or array thereof)
|
58
|
+
# * `block_arg_names` - an arguments string for the argument names that will be passed to the block
|
59
|
+
#
|
60
|
+
# Example:
|
61
|
+
#
|
62
|
+
# > puts method_call_with_block_or_nothing("each", nil, "puts i + 3", "i")
|
63
|
+
# each do |i|
|
64
|
+
# puts i + 3
|
65
|
+
# end
|
66
|
+
def method_call_with_block_or_nothing(method_name, args, block_contents, *block_arg_names)
|
67
|
+
return unless block_contents.present?
|
68
|
+
|
69
|
+
block_args = if block_arg_names.present?
|
70
|
+
" |#{block_arg_names.join(", ")}|"
|
71
|
+
else
|
72
|
+
""
|
73
|
+
end
|
74
|
+
|
75
|
+
unindented_contents = indent(block_contents, 0)
|
76
|
+
code = if unindented_contents.size < 40 && !(unindented_contents =~ /\n/)
|
77
|
+
"#{method_call(method_name, args, true)} {#{block_args} #{unindented_contents} }"
|
78
|
+
else
|
79
|
+
<<-DSL
|
80
|
+
#{method_call(method_name, args)} do#{block_args}
|
81
|
+
#{indent block_contents}
|
82
|
+
end
|
83
|
+
DSL
|
84
|
+
end
|
85
|
+
|
86
|
+
strip_trailing_whitespace(code)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Given a hash of keyword arguments to a method and a second hash containing that method's default arguments,
|
90
|
+
# returns a hash containing all the `keyword_args` omitting the ones that are already the method's default value
|
91
|
+
# for that argument.
|
92
|
+
#
|
93
|
+
# Example:
|
94
|
+
#
|
95
|
+
# > keyword_args_without_defaults({ field_type: 'radio', placeholder_text: 'Enter an answer' }, { field_type: 'default', placeholder_text: 'Enter an answer' })
|
96
|
+
# -> { field_type: 'radio' }
|
97
|
+
def keyword_args_without_defaults(keyword_args, default_args)
|
98
|
+
keyword_args.reject { |key, value| default_args[key] == value }
|
99
|
+
end
|
100
|
+
|
101
|
+
# Given a hash of keyword arguments for a method call, returns a string containing those arguments as they should
|
102
|
+
# be passed to the method.
|
103
|
+
def formatted_keyword_args(keyword_args)
|
104
|
+
keyword_args.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
|
105
|
+
end
|
106
|
+
|
107
|
+
# Given a BlocklyInterpreter::Block, returns a DSL string that will generate that block.
|
108
|
+
def start_block_to_dsl(block)
|
109
|
+
return "" unless block
|
110
|
+
|
111
|
+
block_dsls = block.each_block(false).map do |block|
|
112
|
+
dsl_content = block.to_dsl
|
113
|
+
|
114
|
+
if block.has_comment?
|
115
|
+
dsl_content = method_call_with_possible_block(
|
116
|
+
"with_comment",
|
117
|
+
[block.comment, block.comment_pinned].compact.map(&:inspect).join(", "),
|
118
|
+
dsl_content
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
if block.is_shadow?
|
123
|
+
dsl_content = method_call_with_possible_block("shadow", "", dsl_content)
|
124
|
+
end
|
125
|
+
|
126
|
+
if block.has_position?
|
127
|
+
dsl_content = method_call_with_possible_block("with_position", [block.x, block.y].map(&:inspect).join(", "), dsl_content)
|
128
|
+
end
|
129
|
+
|
130
|
+
dsl_content
|
131
|
+
end
|
132
|
+
block_dsls.join("\n")
|
133
|
+
end
|
134
|
+
|
135
|
+
# Given a Time object (or nil), generates a Ruby string representation of that object.
|
136
|
+
#
|
137
|
+
# Example:
|
138
|
+
#
|
139
|
+
# > puts timestamp_to_dsl(Time.now)
|
140
|
+
# Time.utc(2016, 3, 31, 11, 54, 24, 0, 0)
|
141
|
+
def timestamp_to_dsl(time)
|
142
|
+
return "nil" unless time
|
143
|
+
|
144
|
+
args = [time.year, time.month, time.day, time.hour, time.min, time.sec, time.usec].map(&:inspect).join(", ")
|
145
|
+
"Time.utc(#{args})"
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class BlocklyInterpreter::ExecutionContext
|
4
|
+
attr_reader :interpreter, :early_return_value, :terminated, :debug_messages
|
5
|
+
|
6
|
+
def initialize(interpreter)
|
7
|
+
@interpreter = interpreter
|
8
|
+
@variables = {}
|
9
|
+
@early_return_value = nil
|
10
|
+
@terminated = false
|
11
|
+
@debug_messages = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_variable(name)
|
15
|
+
@variables[name.to_s.upcase]
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_variable(name, value)
|
19
|
+
@variables[name.to_s.upcase] = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_variables(hash)
|
23
|
+
hash.each do |name, value|
|
24
|
+
set_variable(name, value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def merge_state_from(context)
|
29
|
+
end
|
30
|
+
|
31
|
+
def execute(block)
|
32
|
+
current_block = block
|
33
|
+
|
34
|
+
while current_block && !terminated
|
35
|
+
current_block.execute_statement(self)
|
36
|
+
current_block = current_block.next_block
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def early_return!(value = nil)
|
41
|
+
@terminated = true
|
42
|
+
@early_return_value = value
|
43
|
+
end
|
44
|
+
|
45
|
+
def execute_procedure(name, parameters)
|
46
|
+
procedure_block = interpreter.program.procedures[name]
|
47
|
+
|
48
|
+
interpreter.build_execution_context.tap do |proc_context|
|
49
|
+
proc_context.set_variables(@variables.merge(procedure_block.arg_hash(parameters)))
|
50
|
+
proc_context.execute(procedure_block)
|
51
|
+
merge_state_from(proc_context)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def value_for_procedure(name, parameters)
|
56
|
+
procedure_block = interpreter.program.procedures[name]
|
57
|
+
|
58
|
+
proc_context = interpreter.build_execution_context
|
59
|
+
proc_context.set_variables(@variables.merge(procedure_block.arg_hash(parameters)))
|
60
|
+
|
61
|
+
procedure_block.value(proc_context).tap do |value|
|
62
|
+
merge_state_from(proc_context)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_debug_message(message)
|
67
|
+
if message.present?
|
68
|
+
Logger.new(STDERR).debug message
|
69
|
+
debug_messages << message
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|