class SyntaxTree::Pattern
raised.
do not yet support) then a SyntaxTree::Pattern::CompilationError will be
matcher (either because of a syntax error or because it is using syntax we
If the query given to the initializer cannot be compiled into a valid
end
when callable
case node
expression, as in:
guaranteed to respond to #===, which means it itself can be used in a ‘case`
with a single argument, which is the node to match against. It also is
The callable object returned by #compile is guaranteed to respond to #call
callable.call(node)
callable = SyntaxTree::Pattern.new(“Const[value: ’SyntaxTree’]”).compile
expression above into a callable, you would:
necessary callable objects. For example, if you wanted to compile the
parse the expression into a tree, then walk the tree to generate the
the #compile method. This method itself will run back through Syntax Tree to
The pattern gets compiled into an object that responds to call by running
#construct_keys method.
Tree, every node generates these kinds of expressions using the
the pattern is the ‘Const[value: “SyntaxTree”]` expression. Within Syntax
end
in Const[value: “SyntaxTree”]
case node
following snippet:
expression or a rightward assignment expression. For example, in the
expression would normally be passed to an `in` clause within a `case`
A pattern is an object that wraps a Ruby pattern matching expression. The
def combine_and(left, right)
Shortcut for combining two procs into one that returns true if both return
def combine_and(left, right) ->(other) { left.call(other) && right.call(other) } end
def combine_or(left, right)
Shortcut for combining two procs into one that returns true if either
def combine_or(left, right) ->(other) { left.call(other) || right.call(other) } end
def compile
def compile program = begin SyntaxTree.parse("case nil\nin #{query}\nend") rescue Parser::ParseError raise CompilationError, query end compile_node(program.statements.body.first.consequent.pattern) end
def compile_aryptn(node)
def compile_aryptn(node) compile_error(node) if !node.rest.nil? || node.posts.any? constant = node.constant compiled_constant = compile_node(constant) if constant preprocessed = node.requireds.map { |required| compile_node(required) } compiled_requireds = ->(other) do deconstructed = other.deconstruct deconstructed.length == preprocessed.length && preprocessed .zip(deconstructed) .all? { |(matcher, value)| matcher.call(value) } end if compiled_constant combine_and(compiled_constant, compiled_requireds) else compiled_requireds end end
def compile_binary(node)
def compile_binary(node) compile_error(node) if node.operator != :| combine_or(compile_node(node.left), compile_node(node.right)) end
def compile_const(node)
in Ident
def compile_const(node) value = node.value if SyntaxTree.const_defined?(value, false) clazz = SyntaxTree.const_get(value) ->(other) { clazz === other } elsif Object.const_defined?(value, false) clazz = Object.const_get(value) ->(other) { clazz === other } else compile_error(node) end end
def compile_const_path_ref(node)
def compile_const_path_ref(node) parent = node.parent compile_error(node) if !parent.is_a?(VarRef) || !parent.value.is_a?(Const) if parent.value.value == "SyntaxTree" compile_node(node.constant) else compile_error(node) end end
def compile_dyna_symbol(node)
in :""
def compile_dyna_symbol(node) if node.parts.empty? symbol = :"" ->(other) { symbol === other } elsif (value = extract_string(node)) symbol = value.to_sym ->(other) { symbol === other } else compile_error(node) end end
def compile_error(node)
def compile_error(node) raise CompilationError, PP.pp(node, +"").chomp end
def compile_hshptn(node)
in Ident[value: String]
def compile_hshptn(node) compile_error(node) unless node.keyword_rest.nil? compiled_constant = compile_node(node.constant) if node.constant preprocessed = node.keywords.to_h do |keyword, value| compile_error(node) unless keyword.is_a?(Label) [keyword.value.chomp(":").to_sym, compile_node(value)] end compiled_keywords = ->(other) do deconstructed = other.deconstruct_keys(preprocessed.keys) preprocessed.all? do |keyword, matcher| matcher.call(deconstructed[keyword]) end end if compiled_constant combine_and(compiled_constant, compiled_keywords) else compiled_keywords end end
def compile_node(node)
Compile any kind of node. Dispatch out to the individual compilation
def compile_node(node) case node when AryPtn compile_aryptn(node) when Binary compile_binary(node) when Const compile_const(node) when ConstPathRef compile_const_path_ref(node) when DynaSymbol compile_dyna_symbol(node) when HshPtn compile_hshptn(node) when RegexpLiteral compile_regexp_literal(node) when StringLiteral compile_string_literal(node) when SymbolLiteral compile_symbol_literal(node) when VarRef compile_var_ref(node) else compile_error(node) end end
def compile_regexp_literal(node)
def compile_regexp_literal(node) if (value = extract_string(node)) regexp = /#{value}/ ->(attribute) { regexp === attribute } else compile_error(node) end end
def compile_string_literal(node)
in ""
def compile_string_literal(node) if node.parts.empty? ->(attribute) { "" === attribute } elsif (value = extract_string(node)) ->(attribute) { value === attribute } else compile_error(node) end end
def compile_symbol_literal(node)
in :+
def compile_symbol_literal(node) symbol = node.value.value.to_sym ->(attribute) { symbol === attribute } end
def compile_var_ref(node)
in Foo
def compile_var_ref(node) value = node.value if value.is_a?(Const) compile_node(value) elsif value.is_a?(Kw) && value.value.nil? ->(attribute) { nil === attribute } else compile_error(node) end end
def extract_string(node)
plain string content, so this method will extract out the plain string
interpolated expressions, and interpolated variables. We only support
that contain list of parts. This can include plain string content,
There are a couple of nodes (string literals, dynamic symbols, and regexp)
def extract_string(node) parts = node.parts if parts.length == 1 && (part = parts.first) && part.is_a?(TStringContent) part.value end end
def initialize(query)
def initialize(query) @query = query end