blockly_interpreter 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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!
|