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,25 @@
|
|
1
|
+
module BlocklyInterpreter::ExtensionBlocks
|
2
|
+
extend ActiveSupport::Autoload
|
3
|
+
extend BlocklyInterpreter::BlockLibrary
|
4
|
+
|
5
|
+
autoload :DateHashBlock
|
6
|
+
autoload :DateTodayBlock
|
7
|
+
autoload :DebugMessageBlock
|
8
|
+
autoload :ListsAppendBlock
|
9
|
+
autoload :ListsConcatBlock
|
10
|
+
autoload :ListsIncludeOperatorBlock
|
11
|
+
autoload :ObjectPresentBlock
|
12
|
+
autoload :SwitchBlock
|
13
|
+
autoload :TextInflectBlock
|
14
|
+
|
15
|
+
self.block_classes = [
|
16
|
+
DateTodayBlock,
|
17
|
+
DebugMessageBlock,
|
18
|
+
ListsAppendBlock,
|
19
|
+
ListsConcatBlock,
|
20
|
+
ListsIncludeOperatorBlock,
|
21
|
+
ObjectPresentBlock,
|
22
|
+
SwitchBlock,
|
23
|
+
TextInflectBlock
|
24
|
+
]
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class BlocklyInterpreter::ExtensionBlocks::DateTodayBlock < BlocklyInterpreter::Block
|
2
|
+
self.block_type = :date_today
|
3
|
+
|
4
|
+
def value(execution_context)
|
5
|
+
Date.today
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_dsl
|
9
|
+
"date_today"
|
10
|
+
end
|
11
|
+
|
12
|
+
module DSLMethods
|
13
|
+
def date_today
|
14
|
+
block :date_today
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class BlocklyInterpreter::ExtensionBlocks::DebugMessageBlock < BlocklyInterpreter::Block
|
2
|
+
self.block_type = 'debug_message'
|
3
|
+
|
4
|
+
def execute_statement(execution_context)
|
5
|
+
message = values['MESSAGE'].value(execution_context)
|
6
|
+
message = message.inspect unless message.is_a?(String)
|
7
|
+
execution_context.add_debug_message message
|
8
|
+
end
|
9
|
+
|
10
|
+
module DSLMethods
|
11
|
+
def debug_message(msg = nil, &proc)
|
12
|
+
proc ||= Proc.new { text msg }
|
13
|
+
block 'debug_message' do
|
14
|
+
value 'MESSAGE', &proc
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class BlocklyInterpreter::ExtensionBlocks::ListsAppendBlock < BlocklyInterpreter::Block
|
2
|
+
include BlocklyInterpreter::DSLGenerator
|
3
|
+
self.block_type = :lists_append
|
4
|
+
|
5
|
+
def execute_statement(execution_context)
|
6
|
+
list = values['LIST'].value(execution_context)
|
7
|
+
list << values['VALUE'].value(execution_context)
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_dsl
|
11
|
+
method_call_with_possible_block('lists_append', '', [
|
12
|
+
method_call_with_block_or_nothing('list', '', values['LIST']),
|
13
|
+
method_call_with_block_or_nothing('item', '', values['VALUE'])
|
14
|
+
])
|
15
|
+
end
|
16
|
+
|
17
|
+
module DSLMethods
|
18
|
+
class ListsAppendBlockBuilder < BlocklyInterpreter::DSL::BlockBuilder
|
19
|
+
def list(&proc)
|
20
|
+
value("LIST", &proc)
|
21
|
+
end
|
22
|
+
|
23
|
+
def item(&proc)
|
24
|
+
value("VALUE", &proc)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def lists_append
|
29
|
+
@blocks << BlocklyInterpreter::ExtensionBlocks::ListsAppendBlock::DSLMethods::ListsAppendBlockBuilder.new('lists_append').tap do |builder|
|
30
|
+
builder.instance_exec(&proc) if proc
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class BlocklyInterpreter::ExtensionBlocks::ListsConcatBlock < BlocklyInterpreter::Block
|
2
|
+
include BlocklyInterpreter::DSLGenerator
|
3
|
+
self.block_type = :lists_concat
|
4
|
+
|
5
|
+
def value(execution_context)
|
6
|
+
list(execution_context, 1) + list(execution_context, 2)
|
7
|
+
end
|
8
|
+
|
9
|
+
def list(execution_context, n)
|
10
|
+
values["LIST#{n}"].try!(:value, execution_context) || []
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_dsl
|
14
|
+
method_call_with_possible_block("lists_concat", "", [
|
15
|
+
method_call_with_block_or_nothing("list1", "", values['LIST1']),
|
16
|
+
method_call_with_block_or_nothing("list2", "", values['LIST2'])
|
17
|
+
])
|
18
|
+
end
|
19
|
+
|
20
|
+
module DSLMethods
|
21
|
+
class ListsConcatBlockBuilder < BlocklyInterpreter::DSL::BlockBuilder
|
22
|
+
def list1(&proc)
|
23
|
+
value("LIST1", &proc)
|
24
|
+
end
|
25
|
+
|
26
|
+
def list2(&proc)
|
27
|
+
value("LIST2", &proc)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def lists_concat
|
32
|
+
@blocks << BlocklyInterpreter::ExtensionBlocks::ListsConcatBlock::DSLMethods::ListsConcatBlockBuilder.new('lists_concat').tap do |builder|
|
33
|
+
builder.instance_exec(&proc) if proc
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class BlocklyInterpreter::ExtensionBlocks::ListsIncludeOperatorBlock < BlocklyInterpreter::Block
|
2
|
+
self.block_type = :lists_include
|
3
|
+
|
4
|
+
def value(execution_context)
|
5
|
+
a = values['A'].value(execution_context)
|
6
|
+
b = values['B'].value(execution_context)
|
7
|
+
|
8
|
+
case fields['OP']
|
9
|
+
when 'INCLUDE' then a.try(:include?, b)
|
10
|
+
when 'NINCLUDE' then !a.try(:include?, b)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class DSLGenerator < BlocklyInterpreter::DSL::BinaryOperationDSLGenerator
|
15
|
+
def initialize(block)
|
16
|
+
super(block, nil)
|
17
|
+
end
|
18
|
+
|
19
|
+
def dsl_method_name
|
20
|
+
case block.fields['OP']
|
21
|
+
when 'INCLUDE' then "lists_include"
|
22
|
+
when 'NINCLUDE' then "lists_not_include"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_args
|
27
|
+
args = super
|
28
|
+
args.slice(1, args.size - 1)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_dsl
|
33
|
+
DSLGenerator.new(self).dsl
|
34
|
+
end
|
35
|
+
|
36
|
+
module DSLMethods
|
37
|
+
class ListIncludesBlockBuilder < BlocklyInterpreter::DSL::BinaryOperationBlockBuilder
|
38
|
+
end
|
39
|
+
|
40
|
+
def lists_include(a = nil, b = nil, &proc)
|
41
|
+
@blocks << BlocklyInterpreter::ExtensionBlocks::ListsIncludeOperatorBlock::DSLMethods::ListIncludesBlockBuilder.new('lists_include', "INCLUDE", a, b).tap do |builder|
|
42
|
+
builder.instance_exec(&proc) if proc
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def lists_not_include(a = nil, b = nil, &proc)
|
47
|
+
@blocks << BlocklyInterpreter::ExtensionBlocks::ListsIncludeOperatorBlock::DSLMethods::ListIncludesBlockBuilder.new('lists_include', "NINCLUDE", a, b).tap do |builder|
|
48
|
+
builder.instance_exec(&proc) if proc
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class BlocklyInterpreter::ExtensionBlocks::ObjectPresentBlock < BlocklyInterpreter::Block
|
2
|
+
include BlocklyInterpreter::DSLGenerator
|
3
|
+
|
4
|
+
self.block_type = :object_present
|
5
|
+
|
6
|
+
def value(execution_context)
|
7
|
+
values['VALUE'].try!(:value, execution_context).present?
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_dsl
|
11
|
+
method_call_with_possible_block("object_present", "", values['VALUE'])
|
12
|
+
end
|
13
|
+
|
14
|
+
module DSLMethods
|
15
|
+
def object_present(&proc)
|
16
|
+
block :object_present do
|
17
|
+
value :VALUE, &proc
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
class BlocklyInterpreter::ExtensionBlocks::SwitchBlock < BlocklyInterpreter::Block
|
2
|
+
include BlocklyInterpreter::DSLGenerator
|
3
|
+
self.block_type = :controls_switch
|
4
|
+
|
5
|
+
class Conditional
|
6
|
+
attr_reader :predicate_block, :action_block
|
7
|
+
|
8
|
+
def initialize(switch_block, num)
|
9
|
+
@predicate_block = switch_block.values["CASE#{num}"]
|
10
|
+
@action_block = switch_block.statements["DO#{num}"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches?(value, execution_context)
|
14
|
+
predicate_block.value(execution_context) == value
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute_statement(execution_context)
|
18
|
+
execution_context.execute(action_block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def predicates
|
23
|
+
@predicates ||= begin
|
24
|
+
case_count = mutation.try(:[], 'case').try(:to_i) || 0
|
25
|
+
(0...case_count).map { |num| Conditional.new(self, num) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def else_block
|
30
|
+
statements['ELSE']
|
31
|
+
end
|
32
|
+
|
33
|
+
def execute_statement(execution_context)
|
34
|
+
(matching_predicate(execution_context) || else_block).try!(:execute_statement, execution_context)
|
35
|
+
end
|
36
|
+
|
37
|
+
def matching_predicate(execution_context)
|
38
|
+
val = switch_value(execution_context)
|
39
|
+
execution_context.set_variable(fields['CAPTURE_VAR'], val) if fields['CAPTURE_VAR'].present?
|
40
|
+
predicates.detect { |predicate| predicate.matches? val, execution_context }
|
41
|
+
end
|
42
|
+
|
43
|
+
def switch_value(execution_context)
|
44
|
+
values['SWITCH_VAL'].value(execution_context)
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_dsl
|
48
|
+
preamble_dsl = [method_call_with_possible_block('switch_value', '', values['SWITCH_VAL'])]
|
49
|
+
preamble_dsl << "capture_as #{fields['CAPTURE_VAR'].inspect}" if fields['CAPTURE_VAR'].present?
|
50
|
+
|
51
|
+
clauses_dsl = predicates.map do |predicate|
|
52
|
+
[
|
53
|
+
method_call_with_possible_block('predicate', '', predicate.predicate_block),
|
54
|
+
method_call_with_possible_block('action', '', predicate.action_block)
|
55
|
+
].join("\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
clauses_dsl << method_call_with_block_or_nothing('else_action', '', statements['ELSE'])
|
59
|
+
|
60
|
+
method_call_with_possible_block('controls_switch', '', [preamble_dsl.join("\n"), clauses_dsl.join("\n")].join("\n"))
|
61
|
+
end
|
62
|
+
|
63
|
+
module DSLMethods
|
64
|
+
class SwitchBlockBuilder < BlocklyInterpreter::DSL::BlockBuilder
|
65
|
+
attr_reader :predicate_number
|
66
|
+
|
67
|
+
def initialize(block_type)
|
68
|
+
super
|
69
|
+
@predicate_number = 0
|
70
|
+
end
|
71
|
+
|
72
|
+
def capture_as(var_name)
|
73
|
+
field "CAPTURE_VAR", var_name
|
74
|
+
mutation_attr('capture', 1)
|
75
|
+
end
|
76
|
+
|
77
|
+
def switch_value(&proc)
|
78
|
+
value "SWITCH_VAL", &proc
|
79
|
+
end
|
80
|
+
|
81
|
+
def predicate(&proc)
|
82
|
+
value "CASE#{predicate_number}", &proc
|
83
|
+
end
|
84
|
+
|
85
|
+
def action(&proc)
|
86
|
+
statement "DO#{predicate_number}", &proc
|
87
|
+
@predicate_number += 1
|
88
|
+
end
|
89
|
+
|
90
|
+
def else_action(&proc)
|
91
|
+
mutation_attr "else", 1
|
92
|
+
statement "ELSE", &proc
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_xml(node)
|
96
|
+
mutation_attr("case", @predicate_number) if @predicate_number > 0
|
97
|
+
super
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def controls_switch(&proc)
|
102
|
+
@blocks << BlocklyInterpreter::ExtensionBlocks::SwitchBlock::DSLMethods::SwitchBlockBuilder.new("controls_switch").tap do |builder|
|
103
|
+
builder.instance_exec(&proc)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class BlocklyInterpreter::ExtensionBlocks::TextInflectBlock < BlocklyInterpreter::Block
|
2
|
+
INFLECTIONS = %w(
|
3
|
+
humanize
|
4
|
+
pluralize
|
5
|
+
singularize
|
6
|
+
titleize
|
7
|
+
camelize
|
8
|
+
classify
|
9
|
+
dasherize
|
10
|
+
deconstantize
|
11
|
+
demodulize
|
12
|
+
parameterize
|
13
|
+
tableize
|
14
|
+
underscore
|
15
|
+
)
|
16
|
+
|
17
|
+
self.block_type = :text_inflect
|
18
|
+
|
19
|
+
def value(execution_context)
|
20
|
+
text = values['TEXT'].value(execution_context)
|
21
|
+
|
22
|
+
case fields['OP']
|
23
|
+
when *INFLECTIONS then text.public_send(fields['OP'])
|
24
|
+
else text
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class BlocklyInterpreter::GenericBlockDSLGenerator
|
2
|
+
include BlocklyInterpreter::DSLGenerator
|
3
|
+
|
4
|
+
attr_reader :block
|
5
|
+
|
6
|
+
def initialize(block)
|
7
|
+
@block = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def dsl
|
11
|
+
method_call_with_possible_block("block", block.class.block_type.to_sym.inspect, block_contents)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def mutation_attributes_dsl(mutation)
|
16
|
+
mutation.attributes.keys.map do |attribute|
|
17
|
+
"mutation_attr #{attribute.inspect}, #{mutation[attribute].inspect}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def mutation_child_dsl(child)
|
22
|
+
attributes_dsl = child.attributes.keys.map do |child_attribute|
|
23
|
+
"child[#{child_attribute.inspect}] = #{child[child_attribute].inspect}"
|
24
|
+
end
|
25
|
+
|
26
|
+
if child.inner_html.present?
|
27
|
+
attributes_dsl << "child.inner_html = #{child.inner_html.inspect}"
|
28
|
+
end
|
29
|
+
|
30
|
+
method_call_with_possible_block("mutation_child", child.name.inspect, attributes_dsl, "child")
|
31
|
+
end
|
32
|
+
|
33
|
+
def mutation_dsl
|
34
|
+
if block.mutation
|
35
|
+
mutation_attributes_dsl(block.mutation) + block.mutation.children.map do |child|
|
36
|
+
mutation_child_dsl(child)
|
37
|
+
end
|
38
|
+
else
|
39
|
+
[]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def fields_dsl
|
44
|
+
block.fields.map do |key, value|
|
45
|
+
"field #{key.inspect}, #{value.inspect}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def values_dsl
|
50
|
+
block.values.map do |key, value_block|
|
51
|
+
method_call_with_possible_block("value", key.inspect, value_block) + "\n"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def statements_dsl
|
56
|
+
block.statements.map do |key, statement_block|
|
57
|
+
method_call_with_possible_block("statement", key.inspect, statement_block) + "\n"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def block_contents
|
62
|
+
@block_contents ||= (mutation_dsl + fields_dsl + values_dsl + statements_dsl)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
class BlocklyInterpreter::Interpreter
|
4
|
+
attr_reader :program, :debug_messages
|
5
|
+
|
6
|
+
def initialize(program)
|
7
|
+
@program = program
|
8
|
+
@debug_messages = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_execution_context
|
12
|
+
BlocklyInterpreter::ExecutionContext.new(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute
|
16
|
+
build_execution_context.tap do |context|
|
17
|
+
context.execute(program.first_block)
|
18
|
+
add_debug_messages context.debug_messages
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_debug_messages(debug_messages)
|
23
|
+
@debug_messages.push *debug_messages
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
class BlocklyInterpreter::Parser
|
4
|
+
class UnknownBlockTypeError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.registered_block_types
|
8
|
+
@registered_block_types ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.register_block_class(block_class)
|
12
|
+
raise "Block class #{block_class} has no type specified" unless block_class.block_type
|
13
|
+
registered_block_types[block_class.block_type.to_sym] = block_class
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.registered_block_class_for_type(block_type)
|
17
|
+
block_class = registered_block_types[block_type.to_sym]
|
18
|
+
|
19
|
+
if block_class
|
20
|
+
block_class
|
21
|
+
elsif superclass.respond_to?(:registered_block_class_for_type)
|
22
|
+
superclass.registered_block_class_for_type(block_type)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse(xml)
|
27
|
+
dom = Nokogiri::XML.parse(xml)
|
28
|
+
|
29
|
+
if dom.root
|
30
|
+
procs, nonprocs = dom.root.css('> block').partition do |element|
|
31
|
+
['procedures_defreturn', 'procedures_defnoreturn'].include? element['type']
|
32
|
+
end
|
33
|
+
|
34
|
+
procedures = procs.map { |element| block_from_dom(element) }.index_by(&:procedure_name)
|
35
|
+
first_block = block_from_dom(nonprocs.first)
|
36
|
+
else
|
37
|
+
first_block = nil
|
38
|
+
procedures = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
BlocklyInterpreter::Program.new(first_block, procedures)
|
42
|
+
end
|
43
|
+
|
44
|
+
def block_from_dom(element)
|
45
|
+
return unless element
|
46
|
+
block_class_for_element(element).new(
|
47
|
+
element['type'],
|
48
|
+
fields_from_element(element),
|
49
|
+
values_from_element(element),
|
50
|
+
statements_from_element(element),
|
51
|
+
next_block_from_element(element),
|
52
|
+
mutation_from_element(element),
|
53
|
+
comment_from_element(element),
|
54
|
+
comment_pinned_from_element(element),
|
55
|
+
element.name == 'shadow',
|
56
|
+
element['x'],
|
57
|
+
element['y']
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def block_class_for_element(element)
|
64
|
+
block_type = element['type']
|
65
|
+
block_class = self.class.registered_block_class_for_type(block_type)
|
66
|
+
|
67
|
+
if block_class
|
68
|
+
block_class
|
69
|
+
else
|
70
|
+
raise UnknownBlockTypeError.new("#{element['type']} is not a registered block type")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def values_from_element(element)
|
75
|
+
children_by_name(element, '> value') do |child|
|
76
|
+
block_from_dom(child.css('> block').first || child.css('> shadow').first)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def statements_from_element(element)
|
81
|
+
children_by_name(element, '> statement') do |child|
|
82
|
+
block_from_dom(child.css('> block').first || child.css('> shadow').first)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def fields_from_element(element)
|
87
|
+
children_by_name(element, '> field') do |child|
|
88
|
+
child.text
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def next_block_from_element(element)
|
93
|
+
next_block_element = element.css('> next > block').first || element.css('> next > shadow').first
|
94
|
+
block_from_dom(next_block_element) if next_block_element
|
95
|
+
end
|
96
|
+
|
97
|
+
def mutation_from_element(element)
|
98
|
+
element.css('> mutation').first
|
99
|
+
end
|
100
|
+
|
101
|
+
def comment_from_element(element)
|
102
|
+
element.css('> comment').first.try!(:text)
|
103
|
+
end
|
104
|
+
|
105
|
+
def comment_pinned_from_element(element)
|
106
|
+
element.css('> comment').first.try { |comment| comment['pinned'] == 'true' }
|
107
|
+
end
|
108
|
+
|
109
|
+
def children_by_name(xml, selector, &block)
|
110
|
+
xml.css(selector).map do |child|
|
111
|
+
processed_child = block_given? ? yield(child) : child
|
112
|
+
[child['name'], processed_child]
|
113
|
+
end.to_h
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
BlocklyInterpreter::CoreBlocks.register!
|