Class: Aspen::Compiler
- Inherits:
-
Object
- Object
- Aspen::Compiler
- Defined in:
- lib/aspen/compiler.rb
Instance Attribute Summary collapse
-
#environment ⇒ Object
readonly
Returns the value of attribute environment.
-
#root ⇒ Object
readonly
Returns the value of attribute root.
Class Method Summary collapse
Instance Method Summary collapse
- #discourse ⇒ Object
-
#initialize(root, environment = {}) ⇒ Compiler
constructor
A new instance of Compiler.
- #render ⇒ Object
- #visit(node) ⇒ Object
- #visit_attribute(node) ⇒ Object
-
#visit_comment(node) ⇒ Object
This acts as a signal so other methods know to reject comments.
- #visit_content(node) ⇒ Object
- #visit_customstatement(node) ⇒ Object
- #visit_edge(node) ⇒ Object
- #visit_label(node) ⇒ Object
- #visit_narrative(node) ⇒ Object
- #visit_node(node) ⇒ Object
- #visit_statement(node) ⇒ Object
- #visit_type(node) ⇒ Object
Constructor Details
#initialize(root, environment = {}) ⇒ Compiler
TODO:
Make #environment an Aspen::Environment
Returns a new instance of Compiler.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/aspen/compiler.rb', line 17 def initialize(root, environment = {}) @root = root @environment = environment @adapter = environment.fetch(:adapter, :cypher).to_sym # @todo FIXME: This is too much responsibility for the compiler. # This should be delegated to an object and the later calls # just messages to that object. @slug_counters = Hash.new { 1 } # @todo Move this into an Environment object—it should be set there. # and here, just run environment.validate unless Aspen.available_formats.include?(@adapter) raise Aspen::ArgumentError, <<~MSG The adapter, also known as the output format, must be one of: #{Aspen.available_formats.join(', ')}. What Aspen received was #{@adapter}. MSG end end |
Instance Attribute Details
#environment ⇒ Object (readonly)
Returns the value of attribute environment.
7 8 9 |
# File 'lib/aspen/compiler.rb', line 7 def environment @environment end |
#root ⇒ Object (readonly)
Returns the value of attribute root.
7 8 9 |
# File 'lib/aspen/compiler.rb', line 7 def root @root end |
Class Method Details
.render(root, environment = {}) ⇒ Object
TODO:
Make #environment an Aspen::Environment
11 12 13 |
# File 'lib/aspen/compiler.rb', line 11 def self.render(root, environment = {}) new(root, environment).render end |
Instance Method Details
#discourse ⇒ Object
42 43 44 |
# File 'lib/aspen/compiler.rb', line 42 def discourse @discourse ||= Discourse.from_hash(environment) end |
#render ⇒ Object
38 39 40 |
# File 'lib/aspen/compiler.rb', line 38 def render visit(root) end |
#visit(node) ⇒ Object
46 47 48 49 50 |
# File 'lib/aspen/compiler.rb', line 46 def visit(node) short_name = node.class.to_s.split('::').last.downcase method_name = "visit_#{short_name}" send(method_name, node) end |
#visit_attribute(node) ⇒ Object
189 190 191 192 193 |
# File 'lib/aspen/compiler.rb', line 189 def visit_attribute(node) content = visit(node.content) type = visit(node.type) content.send(type.converter) end |
#visit_comment(node) ⇒ Object
This acts as a signal so other methods know to reject comments.
205 206 207 |
# File 'lib/aspen/compiler.rb', line 205 def visit_comment(node) :comment end |
#visit_content(node) ⇒ Object
199 200 201 |
# File 'lib/aspen/compiler.rb', line 199 def visit_content(node) node.content end |
#visit_customstatement(node) ⇒ Object
TODO:
Get the labels back into here. Labelreg? typereg? This is doing too much. Can’t we have typed attributes come from the Grammar?
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/aspen/compiler.rb', line 76 def visit_customstatement(node) statement = visit(node.content) matcher = discourse.grammar.matcher_for(statement) results = matcher.captures(statement) template = matcher.template typereg = matcher.typereg labelreg = matcher.labelreg nodes = [] typed_results = results.inject({}) do |hash, elem| key, value = elem typed_value = case typereg[key] when :integer then value.to_i when :float then value.to_f when :numeric then value.match?(/\./) ? value.to_f : value.to_i when :string then "\"#{value}\"" when :node then # FIXME: This only handles short form. # I think we were allowing grouped and Cypher form to fill # in custom statement templates. # TODO: Add some object to nodes array. node = visit( Aspen::AST::Nodes::Node.new( attribute: value, label: labelreg[key] ) ) nodes << node node end hash[key] = typed_value hash end formatted_results = typed_results.inject({}) do |hash, elem| key, value = elem f_value = value.is_a?(Aspen::Node) ? value.nickname_node : value hash[key] = f_value # TODO: Trying to insert a p_id as well as p to be used in JSON identifiers. # if value.is_a?(Aspen::Node) # hash["#{key}_id"] = value.nickname # end # puts "TYPED VALS: #{hash.inspect}" hash end slugs = template.scan(/{{{?(?<full>uniq_(?<name>\w+))}}}?/).uniq usable_results = if slugs.any? counts = slugs.map do |full, short| [full, "#{short}_#{@slug_counters[full]}"] end.to_h context = results.merge(counts) custom_statement = CustomStatement.new( nodes: nodes, cypher: Mustache.render(template.strip, formatted_results.merge(counts)) ) slugs.each do |full, _| @slug_counters[full] = @slug_counters[full] + 1 end custom_statement else CustomStatement.new( nodes: nodes, cypher: Mustache.render(template.strip, formatted_results) ) end end |
#visit_edge(node) ⇒ Object
163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/aspen/compiler.rb', line 163 def visit_edge(node) content = visit(node.content) unless discourse.allows_edge?(content) raise Aspen::Error, """ Your narrative includes an edge called '#{content}', but only #{discourse.allowed_edges} are allowed. """ end Aspen::Edge.new( content, mutual: discourse.mutual?(visit(node.content)) ) end |
#visit_label(node) ⇒ Object
177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/aspen/compiler.rb', line 177 def visit_label(node) content = visit(node.content) label = Maybe(content).value_or(discourse.default_label) unless discourse.allows_label?(label) raise Aspen::CompileError, """ Your narrative includes a node with label '#{label}', but only #{discourse.allowed_labels} are allowed. """ end label end |
#visit_narrative(node) ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/aspen/compiler.rb', line 52 def visit_narrative(node) # Instead of letting comments be `nil` and using `#compact` # to silently remove them, possibly hiding errors, we "compile" # comments as `:comment` and filter them explicitly statements = node.statements.map do |statement| # This will visit both regular and custom statements. visit(statement) end.reject { |elem| elem == :comment } renderer_klass = Kernel.const_get("Aspen::Renderers::#{@adapter.to_s.downcase.capitalize}Renderer") renderer_klass.new(statements, environment).render end |
#visit_node(node) ⇒ Object
148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/aspen/compiler.rb', line 148 def visit_node(node) # Get the label, falling back to the default label. label = visit(node.label) # Get the attribute name, falling back to the default attribute name. attribute_name = Maybe(nil).value_or(discourse.default_attr_name(label)) typed_attribute_value = visit(node.attribute) nickname = typed_attribute_value.to_s.downcase Aspen::Node.new( label: label, attributes: { attribute_name => typed_attribute_value } ) end |
#visit_statement(node) ⇒ Object
65 66 67 68 69 70 71 |
# File 'lib/aspen/compiler.rb', line 65 def visit_statement(node) Statement.new( origin: visit(node.origin), edge: visit(node.edge), target: visit(node.target) ) end |
#visit_type(node) ⇒ Object
195 196 197 |
# File 'lib/aspen/compiler.rb', line 195 def visit_type(node) node end |