class Yadriggy::Syntax

Syntax checker.

Constants

ArrayLiteral
Block
Exprs
HashLiteral
Parameters
Paren

Attributes

debug[W]

debugging mode is on if true.

hash[R]

@return [Hash] the grammar rules.

Public Class Methods

check_syntax(block) click to toggle source

Checks the syntax of the grammar rules.

@param [ASTnode] block the grammar rules. @return [Boolean] false if a syntax error is found in the given

grammar rules.
# File lib/yadriggy/syntax.rb, line 51
def self.check_syntax(block)
  @syntax.nil? || @syntax.check(block)
end
last_error() click to toggle source

@return [String] the error message for the grammar rule

last checked.
# File lib/yadriggy/syntax.rb, line 57
def self.last_error
  @syntax.error
end
new(ast) click to toggle source

@param [ASTree] ast the grammar rules.

# File lib/yadriggy/syntax.rb, line 62
def initialize(ast)
  @error_loc = nil
  @error_msg = nil
  @debug = false
  @hash = {}
  update_hash(ast.tree.body)
end
ruby_syntax() click to toggle source

Defines Ruby syntax and returns its Syntax object. @return [Syntax] the Ruby syntax.

# File lib/yadriggy/syntax.rb, line 469
def self.ruby_syntax
  Yadriggy.define_syntax do
    expr  <= Name | Number | Super | Binary | Unary | SymbolLiteral |
            ConstPathRef | StringLiteral | StringInterpolation |
            ArrayLiteral | Paren | Call | ArrayRef | HashLiteral |
            Return | ForLoop | Loop | Conditional | Break |
            Lambda | BeginEnd | Def | ModuleDef
    exprs <= Exprs | expr

    Name <= { name: String }
    Number     <= { value: Numeric }
    Super      <= {}
    Identifier <= Name
    SymbolLiteral    <= { name: String }
    VariableCall     <= Name
    InstanceVariable <= Name
    GlobalVariable   <= Name
    Label      <= Name
    Reserved   <= Name
    Const      <= Name
    Binary     <= { left: expr, op: Symbol, right: expr }
    ArrayRef   <= { array: expr, indexes: [ expr ] }
    ArrayRefField  <= ArrayRef
    Assign     <= { left: [expr] | expr, op: Symbol,
                    right: [expr] | expr }
    Dots       <= Binary
    Unary      <= { op: Symbol, operand: expr }
    ConstPathRef   <= { scope: (ConstPathRef | Const), name: Const }
    ConstPathField <= ConstPathRef
    StringLiteral  <= { value: String }
    StringInterpolation <= { contents: [ exprs ] }
    ArrayLiteral   <= { elements: [ expr ] }
    Paren      <= { expression: expr }
    HashLiteral    <= { pairs: [ expr * expr ] }
    Return     <= { values: [ expr ] }
    ForLoop    <= {vars: [ Identifier ], set: expr, body: exprs }
    Loop       <= { op: Symbol, cond: expr, body: exprs }
    Conditional <= { op: Symbol, cond: expr, then: exprs,
                     all_elsif: [expr * exprs], else: (exprs) }
    Parameters <= { params: [ Identifier ],
                   optionals: [ Identifier * expr ],
                   rest_of_params: (Identifier),
                   params_after_rest: [ Identifier ],
                   keywords: [ Label * expr ],
                   rest_of_keywords: (Identifier),
                   block_param: (Identifier) }
    Block      <= Parameters + { body: exprs }
    Lambda     <= Block
    Call       <= { receiver: (expr), op: (Symbol), name: (Identifier),
                    args: [ expr ], block_arg: (expr), block: (Block) }
    Command    <= Call
    Exprs      <= { expressions: [ expr ] }
    Rescue     <= { types: [ Const | ConstPathRef ],
                    parameter: (Identifier),
                    body: (exprs), nested_rescue: (Rescue),
                    else: (exprs), ensure: (exprs) }
    BeginEnd   <= { body: exprs, rescue: (Rescue) }
    Def        <= Parameters +
                  { singular: (expr), name: Identifier, body: exprs,
                    rescue: (Rescue) }
    ModuleDef  <= { name: Const | ConstPathRef, body: exprs,
                    rescue: (Rescue) }
    ClassDef   <= ModuleDef +
                  { superclass: (Const | ConstPathRef) }
    SingularClassDef <= { name: expr, body: exprs,
                    rescue: (Rescue) }
    Program    <= { elements: exprs }
  end
end

Public Instance Methods

add_rules(syntax=nil, &block) click to toggle source

Adds rules. @param [Syntax] syntax the rules of this given syntax are added.

This parameter is optional.

@yield the rules in the block are added.

The block is optional.
# File lib/yadriggy/syntax.rb, line 91
def add_rules(syntax=nil, &block)
  if syntax.is_a?(Syntax)
    syntax.hash.each do |k,v|
      @hash[k] = v
    end
  elsif block.is_a?(Proc)
    ast = Yadriggy::reify(block)
    if Syntax.check_syntax(ast.tree)
      update_hash(ast.tree.body)
    else
      raise Syntax.last_error
    end
  end
end
check(tree) click to toggle source

Checks the syntax of the given AST.

@param [ASTnode] tree the AST. @return [Boolean] true when the given AST is syntactically correct.

# File lib/yadriggy/syntax.rb, line 120
def check(tree)
  error_cleared!
  expr = find_hash_entry(tree.class)
  if expr
    check_expr(expr, tree, false) || error_found!(tree, tree)
  else
    error_found!(tree, tree, "no rule for #{tree.class}")
  end
end
check_error(astree) click to toggle source

Checks the syntax of the given AST and raise an error if a syntax error is found.

@param [ASTree|ASTnode] astree the AST returned by {Yadriggy::reify}. @return [void] @raise [SyntaxError] when a syntax error is found.

# File lib/yadriggy/syntax.rb, line 112
def check_error(astree)
  raise_error unless check(astree.is_a?(ASTree) ? astree.tree : astree)
end
check_usertype(user_type, tree) click to toggle source

Checks whether the given AST matches the grammar rule for the given user type.

@param [String|Symbol] user_type the name of the user type. @param [ASTnode] tree the AST. @return [Boolean] true if the given AST matches.

# File lib/yadriggy/syntax.rb, line 136
def check_usertype(user_type, tree)
  error_cleared!
  check_rule_usertype(user_type.to_s, tree, false)
end
error() click to toggle source

Returns an error message. @return [String] an error message when the last invocation of {#check}

returns false.
# File lib/yadriggy/syntax.rb, line 144
def error
  if @error_loc.nil? && @error_msg.nil?
    ''
  else
    "#{@error_loc} DSL syntax error#{@error_msg}"
  end
end
raise_error() click to toggle source

Raises a syntax-error exception. @raise [SyntaxError] always.

# File lib/yadriggy/syntax.rb, line 154
def raise_error
  raise SyntaxError.new(error)
end
update_hash(body) click to toggle source

@api private @param [Body] body @return [void]

# File lib/yadriggy/syntax.rb, line 73
def update_hash(body)
  return if body.nil?

  if body.is_a?(Binary)
    key = to_hash_key(body.left)
    @hash[key] = body.right
  else
    body.expressions.each do |e|
      @hash[to_hash_key(e.left)] = e.right
    end
  end
end

Private Instance Methods

check_add_expr(expr, ast, in_hash) click to toggle source
# File lib/yadriggy/syntax.rb, line 247
def check_add_expr(expr, ast, in_hash)
  if expr.is_a?(Binary) && expr.op == :+
    check_add_expr(expr.left, ast, in_hash) &&
    check_operand(expr.right, ast, in_hash)
  else
    check_operand(expr, ast, in_hash)
  end
end
check_array_elements(con_elements, ast_elements) click to toggle source
# File lib/yadriggy/syntax.rb, line 332
def check_array_elements(con_elements, ast_elements)
  ast_i = 0
  for i in 0..con_elements.size - 2
    if check_one_array_element(con_elements[i], ast_elements[ast_i])
      ast_i += 1
    else
      unless con_elements[i].is_a?(Paren)
        return error_found!(ast_elements[ast_i], ast_elements)
      end
    end
  end
  con = con_elements.last
  while ast_i < ast_elements.size
    if check_one_array_element(con, ast_elements[ast_i])
      ast_i += 1
    else
      return error_found!(ast_elements[ast_i], ast_elements)
    end
  end
  true
end
check_const_constraint(con, ast) click to toggle source
# File lib/yadriggy/syntax.rb, line 371
def check_const_constraint(con, ast)
  ast.is_a?(to_node_class(con.name)) &&
    check_rule(ast.class, ast, true)
end
check_constraint(con, ast) click to toggle source
# File lib/yadriggy/syntax.rb, line 301
def check_constraint(con, ast)
  if con.is_a?(StringLiteral)
    con.value == ast
  elsif con.is_a?(SymbolLiteral)
    con.to_sym == ast
  elsif con.is_a?(Reserved)
    ast == nil || ast == []
  elsif con.is_a?(Const)
    check_const_constraint(con, mabye_one_element_array(ast))
  elsif con.is_a?(IdentifierOrCall)
    check_rule_usertype(con.name, mabye_one_element_array(ast), true)
  elsif con.is_a?(Paren)
    ast == nil || check_or_constraint(con.expression, ast)
  elsif con.is_a?(ArrayLiteral)
    ast.is_a?(Array) &&
      if con.elements.size == 0
        ast.size == 0
      elsif con.elements.size == 1
        con0 = con.elements[0]
        ast.all? do |e|
          check_one_array_element(con0, e) || error_found!(e, ast)
        end
      else
        con.elements.size - 1 <= ast.size &&
          check_array_elements(con.elements, ast)
      end
  else
    false
  end
end
check_expr(expr, ast, in_hash) click to toggle source
# File lib/yadriggy/syntax.rb, line 238
def check_expr(expr, ast, in_hash)
  if expr.is_a?(Binary) && expr.op == :|
    check_expr(expr.left, ast, in_hash) ||
    check_add_expr(expr.right, ast, in_hash) && error_cleared!
  else
    check_add_expr(expr, ast, in_hash)
  end
end
check_hash(hash, ast) click to toggle source
# File lib/yadriggy/syntax.rb, line 271
def check_hash(hash, ast)
  !ast.nil? && hash.pairs.all? do |p|
    field = p[0].name
    raise_hash_error(field, ast) unless ast.class.method_defined?(field)
    if check_or_constraint(p[1], ast.send(field))
      true
    else
      if @debug
        warn "  failed to check \##{field}"
        @debug = 1
      end
      error_found!(ast.send(field), ast,
                   "#{field} in #{ast.usertype.nil? ? ast.class : ast.usertype}?")
    end
  end
end
check_one_array_element(con, element) click to toggle source
# File lib/yadriggy/syntax.rb, line 354
def check_one_array_element(con, element)
  if element.is_a?(Array)
    check_pair_element(con, element, element.size - 1)
  else
    check_or_constraint(con, element)
  end
end
check_operand(operand, ast, in_hash) click to toggle source
# File lib/yadriggy/syntax.rb, line 256
def check_operand(operand, ast, in_hash)
  if operand.is_a?(Const)
    clazz = to_node_class(operand.name)
    ast.is_a?(clazz) && check_rule(clazz, ast, in_hash)
  elsif operand.is_a?(Reserved)
    ast == nil || ast == []
  elsif operand.is_a?(IdentifierOrCall)
    check_rule_usertype(operand.name, ast, in_hash)
  elsif operand.is_a?(HashLiteral)
    check_hash(operand, ast)
  else
    false
  end
end
check_or_constraint(or_con, ast) click to toggle source
# File lib/yadriggy/syntax.rb, line 292
def check_or_constraint(or_con, ast)
  if or_con.is_a?(Binary) && or_con.op == :|
    check_or_constraint(or_con.left, ast) ||
    check_constraint(or_con.right, ast) && error_cleared!
  else
    check_constraint(or_con, ast)
  end
end
check_pair_element(con, element, idx) click to toggle source
# File lib/yadriggy/syntax.rb, line 362
def check_pair_element(con, element, idx)
  if con.is_a?(Binary) && con.op == :*
    idx > 0 && check_or_constraint(con.right, element[idx]) &&
      check_pair_element(con.left, element, idx - 1)
  else
    idx == 0 && check_or_constraint(con, element[0])
  end
end
check_rule(node_class, ast, in_hash) click to toggle source

Returns true if no rule for the given (non-user) type is found or if ast matches the rule.

# File lib/yadriggy/syntax.rb, line 195
def check_rule(node_class, ast, in_hash)
  if in_hash
    expr = find_hash_entry(ast.class)
  else
    expr = find_hash_entry(node_class)
  end
  result = (expr.nil? || check_expr(expr, ast, false) || error_found!(ast, ast))
  if @debug && !result || @debug == 1
    warn "check rules for #{node_class}, #{result}"
    @debug = true
  end
  result
end
check_rule_usertype(node_type_name, ast, in_hash) click to toggle source
# File lib/yadriggy/syntax.rb, line 218
def check_rule_usertype(node_type_name, ast, in_hash)
  expr = @hash[node_type_name]
  expr && tag_and_check_expr(node_type_name, expr, ast, in_hash) || error_found!(ast, ast)
end
error_cleared!() click to toggle source
# File lib/yadriggy/syntax.rb, line 186
def error_cleared!
  @error_loc = nil
  @error_msg = nil
  true
end
error_found!(ast1, ast2, msg=nil) click to toggle source
# File lib/yadriggy/syntax.rb, line 172
def error_found!(ast1, ast2, msg=nil)
  if @error_loc.nil?
    @error_loc  = if ast1.is_a?(ASTnode)
                    ast1.source_location_string
                  elsif ast2.is_a?(ASTnode)
                    ast2.source_location_string
                  else
                    ''
                  end
  end
  @error_msg = ", #{msg}#{@error_msg}" unless msg.nil?
  false
end
find_hash_entry(node_class) click to toggle source
# File lib/yadriggy/syntax.rb, line 209
def find_hash_entry(node_class)
  expr = @hash[node_class.name]
  if expr.nil? && !node_class.superclass.nil?
    find_hash_entry(node_class.superclass)
  else
    expr
  end
end
mabye_one_element_array(ast) click to toggle source

Rule 'expr <= term' accepts a single element array of term. Recall that 'expr <= [ term ]' also accepts an array of term but the length of the array may be more than one.

# File lib/yadriggy/syntax.rb, line 380
def mabye_one_element_array(ast)
  if ast.is_a?(Array) && ast.size == 1
    ast[0]
  else
    ast
  end
end
raise_hash_error(field, ast) click to toggle source
# File lib/yadriggy/syntax.rb, line 288
def raise_hash_error(field, ast)
  raise SyntaxError.new("unknown method `#{field}' in #{ast.class} tested during syntax checking (wrong grammar?)")
end
tag_and_check_expr(node_type_name, expr, ast, in_hash) click to toggle source

Adds a user-type tag to the given AST.

# File lib/yadriggy/syntax.rb, line 224
def tag_and_check_expr(node_type_name, expr, ast, in_hash)
  return if ast.is_a?(Array)
  unless ast.nil?
    old_usertype = ast.usertype
    ast.usertype = node_type_name.to_sym
  end

  success = check_expr(expr, ast, in_hash)
  unless success || ast.nil?
    ast.usertype = old_usertype
  end
  success
end
to_hash_key(left) click to toggle source
# File lib/yadriggy/syntax.rb, line 164
def to_hash_key(left)
  if left.is_a?(Const)
    to_node_class(left.name).name
  else
    left.name
  end
end
to_node_class(name) click to toggle source
# File lib/yadriggy/syntax.rb, line 160
def to_node_class(name)
  Yadriggy::const_get(name)
end