338 lines
5.7 KiB
Go
338 lines
5.7 KiB
Go
// Copyright ©2020 The go-latex Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package latex // import "github.com/go-latex/latex"
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/go-latex/latex/ast"
|
|
"github.com/go-latex/latex/token"
|
|
)
|
|
|
|
// ParseExpr parses a simple LaTeX expression.
|
|
func ParseExpr(x string) (ast.Node, error) {
|
|
p := newParser(x)
|
|
return p.parse()
|
|
}
|
|
|
|
type state int
|
|
|
|
const (
|
|
normalState state = iota
|
|
mathState
|
|
)
|
|
|
|
type parser struct {
|
|
s *texScanner
|
|
state state
|
|
|
|
macros map[string]macroParser
|
|
}
|
|
|
|
func newParser(x string) *parser {
|
|
p := &parser{
|
|
s: newScanner(strings.NewReader(x)),
|
|
state: normalState,
|
|
}
|
|
p.addBuiltinMacros()
|
|
return p
|
|
}
|
|
|
|
func (p *parser) parse() (ast.Node, error) {
|
|
var nodes ast.List
|
|
for p.s.Next() {
|
|
tok := p.s.Token()
|
|
node := p.parseNode(tok)
|
|
if node == nil {
|
|
continue
|
|
}
|
|
nodes = append(nodes, node)
|
|
}
|
|
|
|
return nodes, nil
|
|
}
|
|
|
|
func (p *parser) next() token.Token {
|
|
if !p.s.Next() {
|
|
return token.Token{Kind: token.EOF}
|
|
}
|
|
return p.s.tok
|
|
}
|
|
|
|
func (p *parser) expect(v rune) {
|
|
p.next()
|
|
if p.s.tok.Text != string(v) {
|
|
panic(fmt.Errorf("expected %q, got %q", v, p.s.tok.Text))
|
|
}
|
|
}
|
|
|
|
func (p *parser) parseNode(tok token.Token) ast.Node {
|
|
switch tok.Kind {
|
|
case token.Comment:
|
|
return nil
|
|
case token.Macro:
|
|
return p.parseMacro(tok)
|
|
case token.Word:
|
|
return p.parseWord(tok)
|
|
case token.Number:
|
|
return p.parseNumber(tok)
|
|
case token.Symbol:
|
|
switch tok.Text {
|
|
case "$":
|
|
return p.parseMathExpr(tok)
|
|
case "^":
|
|
return p.parseSup(tok)
|
|
case "_":
|
|
return p.parseSub(tok)
|
|
default:
|
|
return p.parseSymbol(tok)
|
|
}
|
|
case token.Lbrace:
|
|
switch p.state {
|
|
case mathState:
|
|
return p.parseMathLbrace(tok)
|
|
default:
|
|
panic("not implemented")
|
|
}
|
|
case token.Other:
|
|
switch tok.Text {
|
|
default:
|
|
panic("not implemented: " + tok.String())
|
|
}
|
|
case token.Space:
|
|
switch p.state {
|
|
case mathState:
|
|
return nil
|
|
default:
|
|
return p.parseSymbol(tok)
|
|
}
|
|
|
|
case token.Lparen, token.Rparen,
|
|
token.Lbrack, token.Rbrack:
|
|
return p.parseSymbol(tok)
|
|
|
|
default:
|
|
panic(fmt.Errorf("impossible: %v (%v)", tok, tok.Kind))
|
|
}
|
|
}
|
|
|
|
func (p *parser) parseMathExpr(tok token.Token) ast.Node {
|
|
state := p.state
|
|
p.state = mathState
|
|
defer func() {
|
|
p.state = state
|
|
}()
|
|
|
|
math := &ast.MathExpr{
|
|
Delim: tok.Text,
|
|
Left: tok.Pos,
|
|
}
|
|
var end string
|
|
switch tok.Text {
|
|
case "$":
|
|
end = "$"
|
|
case `\(`:
|
|
end = `\)`
|
|
case `\[`:
|
|
end = `\]`
|
|
case `\begin`:
|
|
panic("not implemented")
|
|
default:
|
|
panic(fmt.Errorf("opening math-expression delimiter %q not supported", tok.Text))
|
|
}
|
|
|
|
loop:
|
|
for p.s.Next() {
|
|
switch p.s.tok.Text {
|
|
case end:
|
|
math.Right = p.s.tok.Pos
|
|
break loop
|
|
default:
|
|
node := p.parseNode(p.s.tok)
|
|
if node == nil {
|
|
continue
|
|
}
|
|
math.List = append(math.List, node)
|
|
}
|
|
}
|
|
|
|
return math
|
|
}
|
|
|
|
func (p *parser) parseMacro(tok token.Token) ast.Node {
|
|
name := tok.Text
|
|
macro, ok := p.macros[name]
|
|
if !ok {
|
|
panic("unknown macro " + name)
|
|
//return nil
|
|
}
|
|
return macro.parseMacro(p)
|
|
}
|
|
|
|
func (p *parser) parseWord(tok token.Token) ast.Node {
|
|
return &ast.Word{
|
|
WordPos: tok.Pos,
|
|
Text: tok.Text,
|
|
}
|
|
}
|
|
|
|
func (p *parser) parseNumber(tok token.Token) ast.Node {
|
|
return &ast.Literal{
|
|
LitPos: tok.Pos,
|
|
Text: tok.Text,
|
|
}
|
|
}
|
|
|
|
func (p *parser) parseMacroArg(macro *ast.Macro) {
|
|
var arg ast.Arg
|
|
p.expect('{')
|
|
arg.Lbrace = p.s.tok.Pos
|
|
|
|
loop:
|
|
for p.s.Next() {
|
|
switch p.s.tok.Kind {
|
|
case token.Rbrace:
|
|
arg.Rbrace = p.s.tok.Pos
|
|
break loop
|
|
default:
|
|
node := p.parseNode(p.s.tok)
|
|
if node == nil {
|
|
continue
|
|
}
|
|
arg.List = append(arg.List, node)
|
|
}
|
|
}
|
|
macro.Args = append(macro.Args, &arg)
|
|
}
|
|
|
|
func (p *parser) parseOptMacroArg(macro *ast.Macro) {
|
|
nxt := p.s.sc.Peek()
|
|
if nxt != '[' {
|
|
return
|
|
}
|
|
|
|
var opt ast.OptArg
|
|
|
|
p.expect('[')
|
|
opt.Lbrack = p.s.tok.Pos
|
|
|
|
loop:
|
|
for p.s.Next() {
|
|
switch p.s.tok.Kind {
|
|
case token.Rbrack:
|
|
opt.Rbrack = p.s.tok.Pos
|
|
break loop
|
|
default:
|
|
node := p.parseNode(p.s.tok)
|
|
if node == nil {
|
|
continue
|
|
}
|
|
opt.List = append(opt.List, node)
|
|
}
|
|
}
|
|
macro.Args = append(macro.Args, &opt)
|
|
}
|
|
|
|
func (p *parser) parseVerbatimMacroArg(macro *ast.Macro) {
|
|
}
|
|
|
|
func (p *parser) parseSup(tok token.Token) ast.Node {
|
|
hat := &ast.Sup{
|
|
HatPos: tok.Pos,
|
|
}
|
|
|
|
switch next := p.s.sc.Peek(); next {
|
|
case '{':
|
|
p.expect('{')
|
|
var list ast.List
|
|
loop:
|
|
for p.s.Next() {
|
|
switch p.s.tok.Kind {
|
|
case token.Rbrace:
|
|
break loop
|
|
default:
|
|
node := p.parseNode(p.s.tok)
|
|
if node == nil {
|
|
continue
|
|
}
|
|
list = append(list, node)
|
|
}
|
|
}
|
|
hat.Node = list
|
|
default:
|
|
hat.Node = p.parseNode(p.next())
|
|
}
|
|
|
|
return hat
|
|
}
|
|
|
|
func (p *parser) parseSub(tok token.Token) ast.Node {
|
|
sub := &ast.Sub{
|
|
UnderPos: tok.Pos,
|
|
}
|
|
|
|
switch next := p.s.sc.Peek(); next {
|
|
case '{':
|
|
p.expect('{')
|
|
var list ast.List
|
|
loop:
|
|
for p.s.Next() {
|
|
switch p.s.tok.Kind {
|
|
case token.Rbrace:
|
|
break loop
|
|
default:
|
|
node := p.parseNode(p.s.tok)
|
|
if node == nil {
|
|
continue
|
|
}
|
|
list = append(list, node)
|
|
}
|
|
}
|
|
sub.Node = list
|
|
default:
|
|
sub.Node = p.parseNode(p.next())
|
|
}
|
|
|
|
return sub
|
|
}
|
|
|
|
func (p *parser) parseSymbol(tok token.Token) ast.Node {
|
|
return &ast.Symbol{
|
|
SymPos: tok.Pos,
|
|
Text: tok.Text,
|
|
}
|
|
}
|
|
|
|
func (p *parser) parseMathLbrace(tok token.Token) ast.Node {
|
|
var (
|
|
lst ast.List
|
|
ldelim = tok.Kind
|
|
rdelim = map[token.Kind]token.Kind{
|
|
token.Lbrace: token.Rbrace,
|
|
token.Lparen: token.Rparen,
|
|
}[ldelim]
|
|
)
|
|
|
|
if rdelim == token.Invalid {
|
|
panic("impossible: no matching right-delim for: " + tok.String())
|
|
}
|
|
|
|
loop:
|
|
for p.s.Next() {
|
|
switch p.s.tok.Kind {
|
|
case rdelim:
|
|
break loop
|
|
default:
|
|
node := p.parseNode(p.s.tok)
|
|
if node == nil {
|
|
continue
|
|
}
|
|
lst = append(lst, node)
|
|
}
|
|
}
|
|
return lst
|
|
}
|