blockly_interpreter 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +4 -0
  4. data/CHANGELOG.md +12 -0
  5. data/COPYING +8 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +91 -0
  8. data/Guardfile +22 -0
  9. data/README.md +72 -0
  10. data/Rakefile +10 -0
  11. data/app/assets/javascripts/blockly_interpreter/extension_blocks.js +1 -0
  12. data/app/assets/javascripts/blockly_interpreter/extension_blocks/dates.js +16 -0
  13. data/app/assets/javascripts/blockly_interpreter/extension_blocks/debugging.js +17 -0
  14. data/app/assets/javascripts/blockly_interpreter/extension_blocks/lists.js +45 -0
  15. data/app/assets/javascripts/blockly_interpreter/extension_blocks/logic.js +274 -0
  16. data/app/assets/javascripts/blockly_interpreter/extension_blocks/text.js.erb +21 -0
  17. data/bin/_guard-core +16 -0
  18. data/bin/console +14 -0
  19. data/bin/guard +16 -0
  20. data/bin/rake +16 -0
  21. data/bin/setup +7 -0
  22. data/blockly_interpreter.gemspec +33 -0
  23. data/exe/blocklyi +13 -0
  24. data/exe/rublocklyc +31 -0
  25. data/lib/blockly_interpreter.rb +25 -0
  26. data/lib/blockly_interpreter/block.rb +113 -0
  27. data/lib/blockly_interpreter/block_library.rb +14 -0
  28. data/lib/blockly_interpreter/console_interpreter.rb +15 -0
  29. data/lib/blockly_interpreter/core_blocks.rb +55 -0
  30. data/lib/blockly_interpreter/core_blocks/arithmetic_operator_block.rb +24 -0
  31. data/lib/blockly_interpreter/core_blocks/boolean_block.rb +23 -0
  32. data/lib/blockly_interpreter/core_blocks/comparison_operator_block.rb +50 -0
  33. data/lib/blockly_interpreter/core_blocks/for_block.rb +68 -0
  34. data/lib/blockly_interpreter/core_blocks/for_each_block.rb +42 -0
  35. data/lib/blockly_interpreter/core_blocks/get_variable_block.rb +19 -0
  36. data/lib/blockly_interpreter/core_blocks/if_block.rb +105 -0
  37. data/lib/blockly_interpreter/core_blocks/lists_create_empty_block.rb +17 -0
  38. data/lib/blockly_interpreter/core_blocks/lists_create_with_block.rb +48 -0
  39. data/lib/blockly_interpreter/core_blocks/lists_get_index_block.rb +95 -0
  40. data/lib/blockly_interpreter/core_blocks/logic_negate_block.rb +20 -0
  41. data/lib/blockly_interpreter/core_blocks/logical_operator_block.rb +22 -0
  42. data/lib/blockly_interpreter/core_blocks/number_block.rb +23 -0
  43. data/lib/blockly_interpreter/core_blocks/procedure_block.rb +49 -0
  44. data/lib/blockly_interpreter/core_blocks/procedures_call_no_return_block.rb +21 -0
  45. data/lib/blockly_interpreter/core_blocks/procedures_call_return_block.rb +21 -0
  46. data/lib/blockly_interpreter/core_blocks/procedures_def_no_return_block.rb +31 -0
  47. data/lib/blockly_interpreter/core_blocks/procedures_def_return_block.rb +64 -0
  48. data/lib/blockly_interpreter/core_blocks/procedures_if_return_block.rb +45 -0
  49. data/lib/blockly_interpreter/core_blocks/repeat_times_block.rb +33 -0
  50. data/lib/blockly_interpreter/core_blocks/set_variable_block.rb +24 -0
  51. data/lib/blockly_interpreter/core_blocks/text_block.rb +23 -0
  52. data/lib/blockly_interpreter/core_blocks/text_change_case_block.rb +32 -0
  53. data/lib/blockly_interpreter/core_blocks/text_join_block.rb +50 -0
  54. data/lib/blockly_interpreter/dsl.rb +291 -0
  55. data/lib/blockly_interpreter/dsl_generator.rb +147 -0
  56. data/lib/blockly_interpreter/engine.rb +8 -0
  57. data/lib/blockly_interpreter/execution_context.rb +72 -0
  58. data/lib/blockly_interpreter/extension_blocks.rb +25 -0
  59. data/lib/blockly_interpreter/extension_blocks/date_today_block.rb +17 -0
  60. data/lib/blockly_interpreter/extension_blocks/debug_message_block.rb +18 -0
  61. data/lib/blockly_interpreter/extension_blocks/lists_append_block.rb +34 -0
  62. data/lib/blockly_interpreter/extension_blocks/lists_concat_block.rb +37 -0
  63. data/lib/blockly_interpreter/extension_blocks/lists_include_operator_block.rb +52 -0
  64. data/lib/blockly_interpreter/extension_blocks/object_present_block.rb +21 -0
  65. data/lib/blockly_interpreter/extension_blocks/switch_block.rb +107 -0
  66. data/lib/blockly_interpreter/extension_blocks/text_inflect_block.rb +27 -0
  67. data/lib/blockly_interpreter/generic_block_dsl_generator.rb +64 -0
  68. data/lib/blockly_interpreter/interpreter.rb +25 -0
  69. data/lib/blockly_interpreter/parser.rb +117 -0
  70. data/lib/blockly_interpreter/program.rb +51 -0
  71. data/lib/blockly_interpreter/program_cache.rb +19 -0
  72. data/lib/blockly_interpreter/test_helper.rb +98 -0
  73. data/lib/blockly_interpreter/version.rb +3 -0
  74. 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!