grammar LengthGrammar

rule term
  additive | factor
end

rule additive
  (factor operator:('+' | '-') space* term) {
    capture(:factor).value.send(capture(:operator).to_s, capture(:term).value)
  }
end

rule factor
  multiplicative | prefix
end

rule multiplicative
  (prefix operator:('*' | '/' | '%') space* factor) {
    capture(:prefix).value.send(capture(:operator).to_s, capture(:factor).value)
  }
end

rule prefix
  prefixed | number_group
end

rule prefixed
  (operator:('-' | '+' | '~') space* prefix) {
    s = capture(:operator).to_s
    s += '@' unless s == '~' # Unary + and - require an @.
    capture(:prefix).value.send(s)
  }
end

rule number_group
  number_set | exponent
end

rule number_set
  (primary space* number_group){
    capture(:primary).value + capture(:number_group).value
  }
end

rule exponent
  exponential | primary
end

rule exponential
  (primary operator:'**' space* prefix) {
    capture(:primary).value.send(capture(:operator).to_s, capture(:prefix).value)
  }
end

rule primary
  group | mixed_number
end

rule group
  (lparen term rparen) {
    capture(:term).value
  }
end

## Lexical syntax

rule mixed_number
  digits_with_unit | number
end

rule digits_with_unit
  (number unit space*){
    RubyUnits::Unit.new capture(:number).value, capture(:unit).to_str
  }
end

rule number
  irrational | integer
end

rule irrational
  rational | float
end

rule float
  decimal | integer
end

rule decimal
  (integer '.' integer) { Rational(capture(:decimal).to_str) }
end

rule integer
  (digits space*) { capture(:digits).to_str.to_i }
end

rule rational
  leading_with_minus_whole | leading_whole | fraction
end

rule leading_with_minus_whole
  (integer operator:('-') fraction){
    capture(:integer).value + capture(:fraction).value
  }
end

rule leading_whole
  (integer fraction){
    capture(:integer).value + capture(:fraction).value
  }
end

rule fraction
  (float '/' float) { Rational(*captures(:float).map(&:to_str).map(&:to_f)) }
end

rule unit
  ([A-z]+ | '"' | '\'' | '˚' )*
end

rule digits
  [0-9]+ ('_' [0-9]+)* # mixed_numbers may contain underscores.
end

rule lparen '(' space* end
rule rparen ')' space* end
rule space  [\s]  end

end