MycoBuilder: BasicObject {

# TODO: be more clever here
const ast: CodeTools::AST::Builder.new

const escape_encodings: {
  hash = ::Ruby::Hash.new
  hash["\\a"] =  7.chr # \a  0x07  Bell or alert
  hash["\\b"] =  8.chr # \b  0x08  Backspace
  # TODO:              # \cx       Control-x
  # TODO:              # \C-x      Control-x
  hash["\\e"] = 27.chr # \e  0x1b  Escape
  hash["\\f"] = 12.chr # \f  0x0c  Formfeed
  # TODO:              # \M-\C-x   Meta-Control-x
  hash["\\n"] = 10.chr # \n  0x0a  Newline
  # TODO:              # \nnn      Octal notation, where n is a digit
  hash["\\r"] = 13.chr # \r  0x0d  Carriage return
  hash["\\s"] = 32.chr # \s  0x20  Space
  hash["\\t"] =  9.chr # \t  0x09  Tab
  hash["\\v"] = 11.chr # \v  0x0b  Vertical tab
  # TODO:              # \xnn      Hexadecimal notation, where n is a digit
  hash
}

# Encode escape characters in string literals
# TODO: rigorously test and refine
encode_escapes: |str| {
  str.gsub(Regexp.new("\\\\.")) |substr| {
    escape_encodings.fetch(substr, substr[-1])
  }
}

# Given a node,op list ([node, op, node, op, ... node]) and operator types,
# collapse the (node, op, node) groups where the operator is one of the types
#
# This function is meant to be called several times on the same list,
# with a different set of operator types each time, in order of precedence.
#
collapse: |input, *types, &block| {
  output = []

  # Scan through, reducing or shifting based on the operator
  Loop.run {
    (input.count > 2) || Loop.break
    n0 = input.shift
    op = input.shift

    types.include?(op.type) &? (
      n1 = input.shift

      result = block
        &? block.call(n0,op,n1)
        ?? ast.invoke(op, n0, op.sym, ast.args(n1, [n1]))
      input.unshift(result)
    ) ?? (
      output.push(n0)
      output.push(op)
    )
  }

  # Push the last item remaining
  output.push(input.shift)

  input.replace(output)
}

}