#
# latex.y
#

class LaTeX::Parser

prechigh
  right CS
preclow

rule

 document : nonbody body nonbody { result = [val[0],val[1],val[2]] }

 nonbody : nonbody NONBODY_LINE { result = (val[0] << val[1]) }
         | /* none */           { result = [] }

 body : BEGIN_DOCUMENT primaries END_DOCUMENT { result = [val[0]] + val[1] + [val[2]] }

 primaries : primaries primary { if val[1] then val[0] << val[1] end; val[0] }
           | /* none */        { result = [] }
 primary : secondary | verbatim_env | comment_env | environment | display_math | inline_math
         | LBRACKET | RBRACKET | AMP { result = StandalonePunctuation.new(val[0]) }

 braced_primary : LBRACE primaries RBRACE { result = Group.new(val[0],val[1],val[2]) } 

 no_math : secondary | environment | comment_env | LBRACKET | RBRACKET
 no_bracket : secondary | verbatim_env | environment | comment_env | inline_math | display_math
 ignore_env : secondary | BEGIN_ENV | END_ENV | display_math | inline_math | LBRACKET | RBRACKET | BEGIN_VERBATIM { raise Racc::ParseError, "Can't have verbatim inside macro argument" }

 ######################################
 no_maths : no_maths no_math { result = (val[0] << val[1]) }
		  | /* none */                { result = [] }

 no_brackets : no_brackets no_bracket { result = (val[0] << val[1]) }
             | /* none */             { result = [] }

 ignore_envs : ignore_envs ignore_env { result = (val[0] << val[1]) }
             | /* none */             { result = [] }
 ######################################

 display_math : DISPLAY_MATH no_maths DISPLAY_MATH { result = Group.new(val[0],val[1],val[2]) }
 inline_math : INLINE_MATH no_maths INLINE_MATH { result = Group.new(val[0],val[1],val[2]) }

 secondary  : control_sequence | word | par | space | BR | COMMENT 
            | inline_verb | PUNCT | braced_primary | STANDALONE_PUNCT { result = StandalonePunctuation.new(val[0]) }

 inline_verb : VERB { result = ControlSequence.new( val[0], nil, []) }
 word : WORD { result = Word.create(val[0]) }
 space : SPACE { result = Whitespace.create(val[0]) }
 par : PAR { result = ParagraphBreak.new(val[0]) }

 # Treat verbatim environments as a sequence of lines
 verbatim_env : BEGIN_VERBATIM verbatim_env_lines END_ENV { result = Environment.new(val[0],[],val[1], val[2],true) }
 verbatim_env_lines : verbatim_env_lines VERB_LINE { result = (val[0] << val[1]) }
                    | /* none */               { result = [] }

 # Remove comment environments altogether
 comment_env : BEGIN_COMMENT comment_env_lines END_ENV { result = nil }
 comment_env_lines : comment_env_lines COMMENT_LINE | /* none */

 environment : BEGIN_ENV macro_args primaries END_ENV { result = Environment.new(val[0],val[1],val[2],val[3]) }

 #############################################
 control_sequence : macro | catcode | let | skip

 let : LET { result = ControlSequence.new(val[0],nil,[]) }

 skip : SKIP { result = ControlSequence.new(val[0],nil,[]) }

 catcode : CATCODE CS { result = ControlSequence.new(val[0],val[1],[]) }

 # A macro invocation with an optional unbraced control sequence as first argument
 # then zero or more braced or bracketed arguments
 macro : CS cs_arg macro_args { result = ControlSequence.new(val[0], val[1], val[2]) }

 cs_arg : CS | /* none */ { result = nil }

 macro_args : macro_args macro_arg  { result = (val[0] << val[1]) }
            | /* none */ { result = [] }

 macro_arg : LBRACE   ignore_envs   RBRACE      { result = Argument.new(val[0],val[1],val[2]) }
           | LBRACKET no_brackets RBRACKET    { result = Argument.new(val[0],val[1],val[2]) }


end

---- header

    require 'latex/scanner'
    require 'latex/parser_support'

---- inner

  class Error < StandardError; end

  attr_reader :seen

  def parse_file(file)
    parse_string(File.read(file))
  end
  
  def parse_string(str,debug=false)
    @yydebug = true
    tokens = Scanner.new.scan(str)
    tokens.reject! { |t| t.first == :COMMENT }
    begin
      tree = parse(tokens)
    rescue
      $stderr.puts seen[-20,20].inspect
      raise
    end
    tree
  end
  
  def parse( tokens )
    @tokens = tokens.dup
    @seen = []
    @tokens.push [false, '$']   # DO NOT FORGET THIS!!!

    do_parse
  end

  def next_token
    @seen << @tokens.shift
    @seen.last
  end

---- footer
