require 'math_ml/latex/builtin'

module MathML
  module LaTeX
    MBEC = /\\.|[^\\]/m

    module RE
      SPACE = /(?:\s|%.*$)/
      NUMERICS = /(?:\.\d+)|(?:\d+(\.\d+)?)/
      OPERATORS = %r{[,.+\-*=/()\[\]<>"|;:!]}
      ALPHABETS = /[a-zA-Z]/
      BLOCK = /\A\{(.*?)\}\z/m
      OPTION = /\A\[(.*)\]\z/m
      COMMANDS = /\\([a-zA-Z]+|[^a-zA-Z])/
      WBSLASH = /\\\\/
      BRACES = /\A([.|\[\]()<>])\z/
    end

    module Font
      NORMAL = 0
      BOLD = 1
      BLACKBOLD = 2
      SCRIPT = 3
      FRAKTUR = 4
      ROMAN = 5
      BOLD_ITALIC = 6
    end

    class BlockNotClosed < StandardError; end
    class NotEnvironment < StandardError; end
    class EnvironmentNotEnd < StandardError; end
    class NeedParameter < StandardError; end
    class EndMismatchToBegin < StandardError; end
    class OptionNotClosed < StandardError; end

    class Scanner < StringScanner
      def done
        string[0, pos]
      end

      def scan_space
        _scan(/#{RE::SPACE}+/)
      end

      def skip_space_and(check_mode)
        opos = pos
        scan_space
        r = yield
        self.pos = opos if check_mode || !r
        r
      end

      unless instance_methods.include?('_eos?')
        alias _eos? eos?
        alias _check check
        alias _scan scan
      end

      def check(re)
        skip_space_and(true) { _check(re) }
      end

      def scan(re)
        skip_space_and(false) { _scan(re) }
      end

      def eos?
        _eos? || _check(/#{RE::SPACE}+\z/)
      end

      def check_command
        check(RE::COMMANDS)
      end

      def scan_command
        scan(RE::COMMANDS)
      end

      def peek_command
        check_command ? self[1] : nil
      end

      def check_block
        skip_space_and(true) { scan_block }
      end

      def scan_block
        return nil unless scan(/\{/)

        block = '{'
        bpos = pos - 1
        nest = 1
        while _scan(/(#{MBEC}*?)([{}])/)
          block << matched
          case self[2]
          when '{'
            nest += 1
          when '}'
            nest -= 1
            break if nest == 0
          end
        end
        if nest > 0
          self.pos = bpos
          raise BlockNotClosed
        end
        self.pos = bpos
        _scan(/\A\{(#{Regexp.escape(block[RE::BLOCK, 1].to_s)})\}/)
      end

      def check_any(remain_space = false)
        skip_space_and(true) { scan_any(remain_space) }
      end

      def scan_any(remain_space = false)
        p = pos
        scan_space
        r = remain_space ? matched.to_s : ''
        case
        when s = scan_block
        when s = scan_command
        else
          unless _scan(/./) || remain_space
            self.pos = p
            return nil
          end
          s = matched.to_s
        end
        r + s
      end

      def scan_option
        return nil unless scan(/\[/)

        opt = '['
        p = pos - 1
        until (s = scan_any(true)) =~ /\A#{RE::SPACE}*\]\z/
          opt << s
          if eos?
            self.pos = p
            raise OptionNotClosed
          end
        end
        opt << s
        self.pos = p
        _scan(/\A\[(#{Regexp.escape(opt[RE::OPTION, 1].to_s)})\]/)
      end

      def check_option
        skip_space_and(true) { scan_option }
      end
    end

    class ParseError < StandardError
      attr_accessor :rest, :done

      def initialize(message, rest = '', done = '')
        @done = done
        @rest = rest
        super(message)
      end

      def inspect
        "#{message} : '#{@done}' / '#{@rest}'\n" + backtrace[0..5].join("\n")
      end
    end

    class Macro
      class Command
        attr_reader :num, :body, :option

        def initialize(n, b, o)
          @num = n
          @body = b
          @option = o
        end
      end

      class Environment
        attr_reader :num, :beginning, :ending, :option

        def initialize(n, b, e, o)
          @num = n
          @beginning = b
          @ending = e
          @option = o
        end
      end

      def initialize
        @commands = {}
        @environments = {}
      end

      def parse_error(message, rest = '', whole = nil)
        rest = whole[/\A.*?(#{Regexp.escape(rest)}.*\z)/, 1] if whole
        rest << @scanner.rest
        done = @scanner.string[0, @scanner.string.size - rest.size]
        ParseError.new(message, rest, done)
      end

      def parse(src)
        @scanner = Scanner.new(src)
        until @scanner.eos?
          unless @scanner.scan_command
            @scanner.scan_space
            raise parse_error('Syntax error.')
          end
          case @scanner[1]
          when 'newcommand'
            parse_newcommand
          when 'newenvironment'
            parse_newenvironment
          else
            raise parse_error('Syntax error.', @scanner.matched)
          end
        end
      rescue BlockNotClosed => e
        raise parse_error('Block not closed.')
      rescue OptionNotClosed => e
        raise parse_error('Option not closed.')
      end

      def scan_num_of_parameter
        if @scanner.scan_option
          unless @scanner[1] =~ /\A#{RE::SPACE}*\d+#{RE::SPACE}*\z/
            raise parse_error('Need positive number.',
                              @scanner[1] + ']')
          end

          @scanner[1].to_i
        else
          0
        end
      end

      def check_parameter_numbers(src, opt, whole)
        s = Scanner.new(src)
        until s.eos?
          case
          when s.scan(/#{MBEC}*?\#(\d+|.)/)
            raise parse_error('Need positive number.') unless s[1] =~ /\d+/
            raise parse_error("Parameter \# too large.", s[1] + s.rest, whole) if s[1].to_i > opt
          else
            return nil
          end
        end
      end

      def parse_newcommand
        if @scanner.scan_block
          s = Scanner.new(@scanner[1])
          raise parse_error('Need newcommand.', s.rest + '}') unless s.scan_command

          com = s[1]
          raise parse_error('Syntax error.', s.rest + '}') unless s.eos?
        elsif @scanner.scan_command
          s = Scanner.new(@scanner[1])
          com = s.scan_command
        else
          raise parse_error('Need newcommand.')
        end

        optnum = scan_num_of_parameter
        opt = @scanner.scan_option ? @scanner[1] : nil

        body = if @scanner.scan_block
                 @scanner[1]
               elsif @scanner.scan_command
                 @scanner.matched
               else
                 @scanner.scan(/./)
               end

        raise parse_error('Need parameter.') unless body

        check_parameter_numbers(body, optnum, @scanner.matched)

        optnum -= 1 if opt
        @commands[com] = Command.new(optnum, body, opt)
      end

      def parse_newenvironment
        if @scanner.scan_block
          env = @scanner[1]
        elsif @scanner.scan_command
          raise ParseError
        elsif @scanner.scan(/./)
          env = @scanner.matched
        end
        raise parse_error('Syntax error.', env[/\A.*?(\\.*\z)/, 1], @scanner.matched) if env =~ /\\/

        optnum = scan_num_of_parameter
        opt = @scanner.scan_option ? @scanner[1] : nil

        b = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
        raise parse_error('Need begin block.') unless b

        check_parameter_numbers(b, optnum, @scanner.matched)
        e = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
        raise parse_error('Need end block.') unless e

        check_parameter_numbers(e, optnum, @scanner.matched)

        optnum -= 1 if opt
        @environments[env] = Environment.new(optnum, b, e, opt)
      end

      def commands(com)
        @commands[com]
      end

      def expand_command(com, params, opt = nil)
        return nil unless @commands.has_key?(com)

        c = @commands[com]
        opt = c.option if c.option && !opt
        params.unshift(opt) if c.option
        raise ParseError, 'Need more parameter.' if params.size < c.num

        c.body.gsub(/(#{MBEC}*?)\#(\d+)/) do
          $1.to_s << params[$2.to_i - 1]
        end
      end

      def environments(env)
        @environments[env]
      end

      def expand_environment(env, body, params, opt = nil)
        return nil unless @environments.has_key?(env)

        e = @environments[env]
        opt = e.option if e.option && !opt
        params.unshift(opt) if e.option
        raise ParseError, 'Need more parameter.' if params.size < e.num

        bg = e.beginning.gsub(/(#{MBEC}*?)\#(\d+)/) do
          $1.to_s << params[$2.to_i - 1]
        end

        en = e.ending.gsub(/(#{MBEC}*?)\#(\d+)/) do
          $1.to_s << params[$2.to_i - 1]
        end

        " #{bg} #{body} #{en} "
      end
    end

    module BuiltinCommands; end
    module BuiltinGroups; end
    module BuiltinEnvironments; end

    class Parser
      class CircularReferenceCommand < StandardError; end

      include LaTeX

      include BuiltinEnvironments
      include BuiltinGroups
      include BuiltinCommands

      BUILTIN_MACRO = <<~'EOS'
        \newenvironment{smallmatrix}{\begin{matrix}}{\end{matrix}}
        \newenvironment{pmatrix}{\left(\begin{matrix}}{\end{matrix}\right)}
        \newenvironment{bmatrix}{\left[\begin{matrix}}{\end{matrix}\right]}
        \newenvironment{Bmatrix}{\left\{\begin{matrix}}{\end{matrix}\right\}}
        \newenvironment{vmatrix}{\left|\begin{matrix}}{\end{matrix}\right|}
        \newenvironment{Vmatrix}{\left\|\begin{matrix}}{\end{matrix}\right\|}
      EOS

      attr_accessor :unsecure_entity
      attr_reader :macro, :symbol_table

      def initialize(opt = {})
        @unsecure_entity = false
        @entities = {}
        @commands = {}
        @symbols = {}
        @delimiters = []
        @group_begins = {}
        @group_ends = {}
        @macro = Macro.new
        @macro.parse(BUILTIN_MACRO)
        @expanded_command = []
        @expanded_environment = []
        @symbol_table = opt[:symbol] || MathML::Symbol::Default
        @symbol_table = MathML::Symbol::MAP[@symbol_table] if @symbol_table.is_a?(::Symbol)

        super()
      end

      def add_entity(list)
        list.each do |i|
          @entities[i] = true
        end
      end

      def parse(src, displaystyle = false)
        @ds = displaystyle
        begin
          parse_into(src, Math.new(@ds), Font::NORMAL)
        rescue ParseError => e
          e.done = src[0...(src.size - e.rest.size)]
          raise
        end
      end

      def push_container(container, scanner = @scanner, font = @font)
        data = [@container, @scanner, @font]
        @container = container
        @scanner = scanner
        @font = font
        begin
          yield container
          container
        ensure
          @container, @scanner, @font = data
        end
      end

      def add_plugin(plugin)
        extend(plugin)
      end

      def add_commands(*a)
        if a.size == 1 && a[0].is_a?(Hash)
          @commands.merge!(a[0])
        else
          a.each { |i| @commands[i] = false }
        end
      end

      def add_multi_command(m, *a)
        a.each { |i| @commands[i] = m }
      end

      def add_sym_cmd(hash)
        @symbols.merge!(hash)
      end

      def add_delimiter(list)
        @delimiters.concat(list)
      end

      def add_group(begin_name, end_name, method = nil)
        @group_begins[begin_name] = method
        @group_ends[end_name] = begin_name
      end

      private

      def parse_into(src, parent, font = nil)
        orig = [@scanner, @container, @font, @ds]
        @scanner = Scanner.new(src)
        @container = parent
        @font = font if font
        begin
          @container << parse_to_element(true) until @scanner.eos?
          @container
        rescue BlockNotClosed => e
          raise ParseError.new('Block not closed.', @scanner.rest)
        rescue NotEnvironment => e
          raise ParseError.new('Not environment.', @scanner.rest)
        rescue EnvironmentNotEnd => e
          raise ParseError.new('Environment not end.', @scanner.rest)
        rescue OptionNotClosed => e
          raise ParseError.new('Option not closed.', @scanner.rest)
        rescue ParseError => e
          e.rest += @scanner.rest.to_s
          raise
        ensure
          @scanner, @container, @font, @ds = orig
        end
      end

      def parse_any(message = 'Syntax error.')
        raise ParseError, message unless @scanner.scan_any

        s = @scanner
        @scanner = Scanner.new(@scanner.matched)
        begin
          parse_to_element
        ensure
          @scanner = s
        end
      end

      def parse_to_element(whole_group = false)
        if whole_group && @group_begins.has_key?(@scanner.peek_command)
          @scanner.scan_command
          parse_group
        elsif @scanner.scan(RE::NUMERICS)
          parse_num
        elsif @scanner.scan(RE::ALPHABETS)
          parse_char
        elsif @scanner.scan(RE::OPERATORS)
          parse_operator
        elsif @scanner.scan_block
          parse_block
        elsif @scanner.scan(/_/)
          parse_sub
        elsif @scanner.scan(/'+|\^/)
          parse_sup
        elsif @scanner.scan(/~/)
          Space.new('1em')
        elsif @scanner.scan_command
          parse_command
        else
          raise ParseError, 'Syntax error.'
        end
      end

      def parse_num
        n = Number.new
        n.extend(Variant).variant = Variant::BOLD if @font == Font::BOLD
        n << @scanner.matched
      end

      def parse_char
        c = @scanner.matched
        i = Identifier.new
        case @font
        when Font::ROMAN
          i.extend(Variant).variant = Variant::NORMAL
        when Font::BOLD
          i.extend(Variant).variant = Variant::BOLD
        when Font::BOLD_ITALIC
          i.extend(Variant).variant = Variant::BOLD_ITALIC
        when Font::BLACKBOLD
          c = symbol_table.convert("#{c}opf")
        when Font::SCRIPT
          c = symbol_table.convert("#{c}scr")
        when Font::FRAKTUR
          c = symbol_table.convert("#{c}fr")
        end
        i << c
      end

      def parse_operator
        o = @scanner.matched
        Operator.new.tap { |op| op[:stretchy] = 'false' } << o
      end

      def parse_block
        os = @scanner
        @scanner = Scanner.new(@scanner[1])
        begin
          push_container(Row.new) do |r|
            r << parse_to_element(true) until @scanner.eos?
          end
        rescue ParseError => e
          e.rest << '}'
          raise
        ensure
          @scanner = os
        end
      end

      def parse_sub
        e = @container.pop
        e ||= None.new
        e = SubSup.new(@ds && e.display_style, e) unless e.is_a?(SubSup)
        raise ParseError.new('Double subscript.', '_') if e.sub

        e.sub = parse_any('Subscript not exist.')
        e
      end

      def parse_sup
        e = @container.pop
        e ||= None.new
        e = SubSup.new(@ds && e.display_style, e) unless e.is_a?(SubSup)
        raise ParseError.new('Double superscript.', @scanner[0]) if e.sup

        if /'+/=~@scanner[0]
          prime = Operator.new
          @scanner[0].size.times do
            prime << symbol_table.convert('prime')
          end
          unless @scanner.scan(/\^/)
            e.sup = prime
            return e
          end
        end
        sup = parse_any('Superscript not exist.')

        if prime
          unless sup.is_a?(Row)
            r = Row.new
            r << sup
            sup = r
          end
          sup.contents.insert(0, prime)
        end

        e.sup = sup
        e
      end

      def entitize(str)
        MathML.pcstring(str.sub(/^(.*)$/) { "&#{$1};" }, true)
      end

      def parse_symbol_command(com, plain = false)
        unless @symbols.include?(com)
          @scanner.pos = @scanner.pos - (com.size + 1)
          raise ParseError, 'Undefined command.'
        end
        data = @symbols[com]
        return nil unless data

        data, s = data
        su = data[0]
        el = data[1]
        el ||= :o
        s ||= com.dup.to_sym
        s = com if s.is_a?(String) && s.length == 0

        case el
        when :I
          el = Identifier.new
        when :i
          el = Identifier.new
          el.extend(Variant).variant = Variant::NORMAL unless s.is_a?(String) && s.length > 1
        when :o
          el = Operator.new
          el[:stretchy] = 'false'
        when :n
          el = Number.new
        else
          raise ParseError, 'Inner data broken.'
        end

        case s
        when Integer
          s = MathML.pcstring("&\#x#{s.to_s(16)};", true)
        when ::Symbol
          s = symbol_table.convert(s)
        else
          MathML.pcstring(s, true)
        end

        return s if plain

        el << s
        el.as_display_style if su == :u
        el
      end

      def parse_command
        com = @scanner[1]
        matched = @scanner.matched
        pos = @scanner.pos - matched.size
        macro = @macro.commands(com)
        if macro
          begin
            flg = @expanded_command.include?(com)
            @expanded_command.push(com)
            raise CircularReferenceCommand if flg

            option = macro.option && @scanner.scan_option ? @scanner[1] : nil
            params = []
            (1..macro.num).each do
              params << (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
              raise ParseError, 'Need more parameter.' unless params.last
            end
            r = parse_into(@macro.expand_command(com, params, option), [])
            return r
          rescue CircularReferenceCommand
            if @expanded_command.size > 1
              raise
            else
              @scanner.pos = pos
              raise ParseError, 'Circular reference.'
            end
          rescue ParseError => e
            if @expanded_command.size > 1
              raise
            else
              @scanner.pos = pos
              raise ParseError, %[Error in macro(#{e.message} "#{e.rest.strip}").]
            end
          ensure
            @expanded_command.pop
          end
        elsif @commands.key?(com)
          m = @commands[com]
          m ||= com
          return __send__("cmd_#{m}")
        end
        parse_symbol_command(com)
      end

      def parse_mathfont(font)
        f = @font
        @font = font
        begin
          push_container(Row.new) { |r| r << parse_any }
        ensure
          @font = f
        end
      end

      def parse_group
        font = @font
        begin
          g = @group_begins[@scanner[1]]
          g ||= @scanner[1]
          __send__("grp_#{g}")
        ensure
          @font = font
        end
      end
    end

    module BuiltinCommands
      OVERS = { 'hat' => 'circ', 'breve' => 'smile', 'grave' => 'grave',
                'acute' => 'acute', 'dot' => 'sdot', 'ddot' => 'nldr', 'dddot' => 'mldr', 'tilde' => 'tilde',
                'bar' => 'macr', 'vec' => 'rightarrow', 'check' => 'vee', 'widehat' => 'circ',
                'overline' => 'macr', 'widetilde' => 'tilde', 'overbrace' => 'OverBrace' }
      UNDERS = { 'underbrace' => 'UnderBrace', 'underline' => 'macr' }

      def initialize
        add_commands('\\' => :backslash)
        add_commands('entity', 'stackrel', 'frac', 'sqrt', 'mbox')
        add_multi_command(:hat_etc, *OVERS.keys)
        add_multi_command(:underbrace_etc, *UNDERS.keys)
        add_multi_command(:quad_etc, ' ', 'quad', 'qquad', ',', ':', ';', '!')
        add_multi_command(:it_etc, 'it', 'rm', 'bf')
        add_multi_command(:mathit_etc, 'mathit', 'mathrm', 'mathbf', 'bm', 'mathbb', 'mathscr', 'mathfrak')
        add_sym_cmd(Builtin::Symbol::MAP)
        add_delimiter(Builtin::Symbol::DELIMITERS)

        super
      end

      def cmd_backslash
        @ds ? nil : XMLElement.new('br', 'xmlns' => 'http://www.w3.org/1999/xhtml')
      end

      def cmd_hat_etc
        com = @scanner[1]
        Over.new(parse_any, Operator.new << entitize(OVERS[com]))
      end

      def cmd_underbrace_etc
        com = @scanner[1]
        Under.new(parse_any, Operator.new << entitize(UNDERS[com]))
      end

      def cmd_entity
        param = @scanner.scan_block ? @scanner[1] : @scanner.scan(/./)
        raise ParseError, 'Need parameter.' unless param

        unless @unsecure_entity || @entities[param]
          param = @scanner.matched[/\A\{#{RE::SPACE}*(.*\})\z/, 1] if @scanner.matched =~ RE::BLOCK
          @scanner.pos = @scanner.pos - param.size
          raise ParseError, 'Unregistered entity.'
        end
        Operator.new << entitize(param)
      end

      def cmd_stackrel
        o = parse_any
        b = parse_any
        Over.new(b, o)
      end

      def cmd_quad_etc
        case @scanner[1]
        when ' '
          Space.new('1em')
        when 'quad'
          Space.new('1em')
        when 'qquad'
          Space.new('2em')
        when ','
          Space.new('0.167em')
        when ':'
          Space.new('0.222em')
        when ';'
          Space.new('0.278em')
        when '!'
          Space.new('-0.167em')
        end
      end

      def cmd_it_etc
        case @scanner[1]
        when 'it'
          @font = Font::NORMAL
        when 'rm'
          @font = Font::ROMAN
        when 'bf'
          @font = Font::BOLD
        end
        nil
      end

      def cmd_mathit_etc
        case @scanner[1]
        when 'mathit'
          parse_mathfont(Font::NORMAL)
        when 'mathrm'
          parse_mathfont(Font::ROMAN)
        when 'mathbf'
          parse_mathfont(Font::BOLD)
        when 'bm'
          parse_mathfont(Font::BOLD_ITALIC)
        when 'mathbb'
          parse_mathfont(Font::BLACKBOLD)
        when 'mathscr'
          parse_mathfont(Font::SCRIPT)
        when 'mathfrak'
          parse_mathfont(Font::FRAKTUR)
        end
      end

      def cmd_frac
        n = parse_any
        d = parse_any
        Frac.new(n, d)
      end

      def cmd_sqrt
        if @scanner.scan_option
          i = parse_into(@scanner[1], [])
          i = i.size == 1 ? i[0] : (Row.new << i)
          b = parse_any
          Root.new(i, b)
        else
          Sqrt.new << parse_any
        end
      end

      def cmd_mbox
        @scanner.scan_any
        Text.new << (@scanner.matched =~ RE::BLOCK ? @scanner[1] : @scanner.matched)
      end
    end

    module BuiltinGroups
      class CircularReferenceEnvironment < StandardError; end

      def initialize
        add_group('begin', 'end')
        add_group('left', 'right', :left_etc)
        add_group('bigg', 'bigg', :left_etc)
        @environments = {}

        super
      end

      def add_environment(*a)
        @environments ||= {}
        if a.size == 1 && a[0].is_a?(Hash)
          @environments.merge!(hash)
        else
          a.each { |i| @environments[i] = false }
        end
      end

      def grp_begin
        matched = @scanner.matched
        begin_pos = @scanner.pos - matched.size
        en = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
        raise ParseError, 'Environment name not exist.' unless en

        macro = @macro.environments(en)
        if macro
          begin
            flg = @expanded_environment.include?(en)
            @expanded_environment.push(en)
            raise CircularReferenceEnvironment if flg

            pos = @scanner.pos
            option = macro.option && @scanner.scan_option ? @scanner[1] : nil
            params = []
            (1..macro.num).each do
              params << (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
              raise ParseError, 'Need more parameter.' unless params.last
            end
            body = ''
            grpnest = 0
            until @scanner.peek_command == 'end' && grpnest == 0
              if @scanner.eos?
                @scanner.pos = pos
                raise ParseError, 'Matching \end not exist.'
              end
              com = @scanner.peek_command
              grpnest += 1 if @group_begins.has_key?(com)
              grpnest -= 1 if @group_ends.has_key?(com) && @group_begins[com]
              raise ParseError, 'Syntax error.' if grpnest < 0

              body << @scanner.scan_any(true)
            end
            @scanner.scan_command
            unless en == (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
              raise ParseError.new('Environment mismatched.',
                                   @scanner.matched)
            end

            begin
              return parse_into(@macro.expand_environment(en, body, params, option), [])
            rescue CircularReferenceEnvironment
              if @expanded_environment.size > 1
                raise
              else
                @scanner.pos = begin_pos
                raise ParseError, 'Circular reference.'
              end
            rescue ParseError => e
              if @expanded_environment.size > 1
                raise
              else
                @scanner.pos = begin_pos
                raise ParseError, %[Error in macro(#{e.message} "#{e.rest.strip}").]
              end
            end
          ensure
            @expanded_environment.pop
          end
        end

        raise ParseError, 'Undefined environment.' unless @environments.has_key?(en)

        e = @environments[en]
        e ||= en # default method name
        __send__("env_#{e}")
      end

      def grp_left_etc
        right =
          case @scanner[1]
          when 'left'
            'right'
          when 'bigg'
            'bigg'
          end

        f = Fenced.new
        p = @scanner.pos
        o = @scanner.scan_any
        raise ParseError, 'Need brace here.' unless o && (o =~ RE::BRACES || @delimiters.include?(o[RE::COMMANDS, 1]))

        f.open = (o =~ RE::BRACES ? o : parse_symbol_command(o[RE::COMMANDS, 1], true))
        f << push_container(Row.new) do |r|
          until @scanner.peek_command == right
            if @scanner.eos?
              @scanner.pos = p
              raise ParseError, 'Brace not closed.'
            end
            r << parse_to_element(true)
          end
        end
        @scanner.scan_command # skip right
        c = @scanner.scan_any
        raise ParseError, 'Need brace here.' unless c =~ RE::BRACES || @delimiters.include?(c[RE::COMMANDS, 1])

        f.close = (c =~ RE::BRACES ? c : parse_symbol_command(c[RE::COMMANDS, 1], true))
        f
      end
    end

    module BuiltinEnvironments
      def initialize
        add_environment('array', 'matrix')

        super
      end

      def env_array
        layout = @scanner.scan_block ? @scanner.matched : @scanner.scan(/./)
        l = Scanner.new(layout =~ RE::BLOCK ? layout[RE::BLOCK, 1] : layout)
        t = Table.new
        aligns = []
        vlines = []
        vlined = l.check(/\|/)
        columned = false
        until l.eos?
          c = l.scan_any
          unless c =~ /[clr|@]/
            raise ParseError.new(
              'Syntax error.',
              layout[/\A.*(#{Regexp.escape(c + l.rest)}.*\z)/m, 1]
            )
          end

          if c == '|'
            aligns << Align::CENTER if vlined
            vlines << Line::SOLID
            vlined = true
            columned = false
          else
            vlines << Line::NONE if columned
            vlined = false
            columned = true
            case c
            when 'l'
              aligns << Align::LEFT
            when 'c'
              aligns << Align::CENTER
            when 'r'
              aligns << Align::RIGHT
            when '@'
              aligns << Align::CENTER
              l.scan_any
            end
          end
        end
        t.aligns = aligns
        t.vlines = vlines

        layout = layout[RE::BLOCK, 1] if layout =~ RE::BLOCK
        raise ParseError, 'Need parameter here.' if layout == ''

        hlines = []
        row_parsed = false
        hlined = false
        until @scanner.peek_command == 'end'
          raise ParseError, 'Matching \end not exist.' if @scanner.eos?

          if @scanner.peek_command == 'hline'
            @scanner.scan_command
            t << Tr.new unless row_parsed
            hlines << Line::SOLID
            row_parsed = false
            hlined = true
          else
            hlines << Line::NONE if row_parsed
            t << env_array_row(l.string)
            @scanner.scan(RE::WBSLASH)
            row_parsed = true
            hlined = false
          end
        end
        t.hlines = hlines

        if hlined
          tr = Tr.new
          (0..vlines.size).each { |_i| tr << Td.new }
          t << tr
        end

        @scanner.scan_command
        raise ParseError, 'Environment mismatched.' unless @scanner.check_block && @scanner[1] == 'array'

        @scanner.scan_block
        t
      end

      def env_array_row(layout)
        l = Scanner.new(layout)
        r = Tr.new
        first_column = true
        vlined = l.check(/\|/)
        until l.eos?
          c = l.scan(/./)
          if c == '|'
            r << Td.new if vlined
            vlined = true
            next
          else
            vlined = false
            case c
            when 'r', 'l', 'c'
            when '@'
              r << parse_into(l.scan_any, Td.new)
              next
            end
            if first_column
              first_column = false
            else
              raise ParseError.new('Need more column.', @scanner.matched.to_s) unless @scanner.scan(/&/)
            end
            r << push_container(Td.new) do |td|
              td << parse_to_element(true) until @scanner.peek_command == 'end' ||
                                                 @scanner.check(/(&|\\\\)/) || @scanner.eos?
            end
          end
        end
        r << Td.new if vlined
        raise ParseError, 'Too many column.' if @scanner.check(/&/)

        r
      end

      def env_matrix
        t = Table.new
        hlines = []
        hlined = false
        row_parsed = false
        until @scanner.peek_command == 'end'
          raise ParseError, 'Matching \end not exist.' if @scanner.eos?

          if @scanner.peek_command == 'hline'
            @scanner.scan_command
            t << Tr.new unless row_parsed
            hlines << Line::SOLID
            row_parsed = false
            hlined = true
          else
            hlines << Line::NONE if row_parsed
            t << (r = Tr.new)
            r << (td = Td.new)
            until @scanner.check(RE::WBSLASH) || @scanner.peek_command == 'end' || @scanner.eos?
              push_container(td) do |e|
                e << parse_to_element(true) until @scanner.peek_command == 'end' ||
                                                  @scanner.check(/(&|\\\\)/) || @scanner.eos?
              end
              r << (td = Td.new) if @scanner.scan(/&/)
            end
            @scanner.scan(RE::WBSLASH)
            row_parsed = true
            hlined = false
          end
        end
        t.hlines = hlines

        t << Tr.new if hlined

        raise ParseError, 'Need \\end{array}.' unless @scanner.peek_command == 'end'

        @scanner.scan_command
        raise ParseError, 'Environment mismatched.' unless @scanner.check_block && @scanner[1] == 'matrix'

        @scanner.scan_block
        t
      end

      def env_matrix_row
        r = Tr.new
        until @scanner.check(RE::WBSLASH) || @scanner.peek_command == 'end'
          r << push_container(Td.new) do |td|
            td << parse_to_element(true) until @scanner.peek_command == 'end' ||
                                               @scanner.check(/(&|\\\\)/) || @scanner.eos?
          end
        end
      end
    end
  end
end
