mirror of
https://github.com/romychs/Ocean-240.2-Emulator.git
synced 2026-04-21 11:03:21 +03:00
148 lines
4.0 KiB
Go
148 lines
4.0 KiB
Go
package gval
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
"text/scanner"
|
|
"unicode"
|
|
)
|
|
|
|
// Parser parses expressions in a Language into an Evaluable
|
|
type Parser struct {
|
|
scanner scanner.Scanner
|
|
Language
|
|
lastScan rune
|
|
camouflage error
|
|
}
|
|
|
|
func newParser(expression string, l Language) *Parser {
|
|
sc := scanner.Scanner{}
|
|
sc.Init(strings.NewReader(expression))
|
|
sc.Error = func(*scanner.Scanner, string) {}
|
|
sc.Filename = expression + "\t"
|
|
p := &Parser{scanner: sc, Language: l}
|
|
p.resetScannerProperties()
|
|
return p
|
|
}
|
|
|
|
func (p *Parser) resetScannerProperties() {
|
|
p.scanner.Whitespace = scanner.GoWhitespace
|
|
p.scanner.Mode = scanner.GoTokens
|
|
p.scanner.IsIdentRune = func(r rune, pos int) bool {
|
|
return unicode.IsLetter(r) || r == '_' || (pos > 0 && unicode.IsDigit(r))
|
|
}
|
|
}
|
|
|
|
// SetWhitespace sets the behavior of the whitespace matcher. The given
|
|
// characters must be less than or equal to 0x20 (' ').
|
|
func (p *Parser) SetWhitespace(chars ...rune) {
|
|
var mask uint64
|
|
for _, char := range chars {
|
|
mask |= 1 << uint(char)
|
|
}
|
|
|
|
p.scanner.Whitespace = mask
|
|
}
|
|
|
|
// SetMode sets the tokens that the underlying scanner will match.
|
|
func (p *Parser) SetMode(mode uint) {
|
|
p.scanner.Mode = mode
|
|
}
|
|
|
|
// SetIsIdentRuneFunc sets the function that matches ident characters in the
|
|
// underlying scanner.
|
|
func (p *Parser) SetIsIdentRuneFunc(fn func(ch rune, i int) bool) {
|
|
p.scanner.IsIdentRune = fn
|
|
}
|
|
|
|
// Scan reads the next token or Unicode character from source and returns it.
|
|
// It only recognizes tokens t for which the respective Mode bit (1<<-t) is set.
|
|
// It returns scanner.EOF at the end of the source.
|
|
func (p *Parser) Scan() rune {
|
|
if p.isCamouflaged() {
|
|
p.camouflage = nil
|
|
return p.lastScan
|
|
}
|
|
p.camouflage = nil
|
|
p.lastScan = p.scanner.Scan()
|
|
return p.lastScan
|
|
}
|
|
|
|
func (p *Parser) isCamouflaged() bool {
|
|
return p.camouflage != nil && p.camouflage != errCamouflageAfterNext
|
|
}
|
|
|
|
// Camouflage rewind the last Scan(). The Parser holds the camouflage error until
|
|
// the next Scan()
|
|
// Do not call Rewind() on a camouflaged Parser
|
|
func (p *Parser) Camouflage(unit string, expected ...rune) {
|
|
if p.isCamouflaged() {
|
|
panic(fmt.Errorf("can only Camouflage() after Scan(): %w", p.camouflage))
|
|
}
|
|
p.camouflage = p.Expected(unit, expected...)
|
|
}
|
|
|
|
// Peek returns the next Unicode character in the source without advancing
|
|
// the scanner. It returns EOF if the scanner's position is at the last
|
|
// character of the source.
|
|
// Do not call Peek() on a camouflaged Parser
|
|
func (p *Parser) Peek() rune {
|
|
if p.isCamouflaged() {
|
|
panic("can not Peek() on camouflaged Parser")
|
|
}
|
|
return p.scanner.Peek()
|
|
}
|
|
|
|
var errCamouflageAfterNext = fmt.Errorf("Camouflage() after Next()")
|
|
|
|
// Next reads and returns the next Unicode character.
|
|
// It returns EOF at the end of the source.
|
|
// Do not call Next() on a camouflaged Parser
|
|
func (p *Parser) Next() rune {
|
|
if p.isCamouflaged() {
|
|
panic("can not Next() on camouflaged Parser")
|
|
}
|
|
p.camouflage = errCamouflageAfterNext
|
|
return p.scanner.Next()
|
|
}
|
|
|
|
// TokenText returns the string corresponding to the most recently scanned token.
|
|
// Valid after calling Scan().
|
|
func (p *Parser) TokenText() string {
|
|
return p.scanner.TokenText()
|
|
}
|
|
|
|
// Expected returns an error signaling an unexpected Scan() result
|
|
func (p *Parser) Expected(unit string, expected ...rune) error {
|
|
return unexpectedRune{unit, expected, p.lastScan}
|
|
}
|
|
|
|
type unexpectedRune struct {
|
|
unit string
|
|
expected []rune
|
|
got rune
|
|
}
|
|
|
|
func (err unexpectedRune) Error() string {
|
|
exp := bytes.Buffer{}
|
|
runes := err.expected
|
|
switch len(runes) {
|
|
default:
|
|
for _, r := range runes[:len(runes)-2] {
|
|
exp.WriteString(scanner.TokenString(r))
|
|
exp.WriteString(", ")
|
|
}
|
|
fallthrough
|
|
case 2:
|
|
exp.WriteString(scanner.TokenString(runes[len(runes)-2]))
|
|
exp.WriteString(" or ")
|
|
fallthrough
|
|
case 1:
|
|
exp.WriteString(scanner.TokenString(runes[len(runes)-1]))
|
|
case 0:
|
|
return fmt.Sprintf("unexpected %s while scanning %s", scanner.TokenString(err.got), err.unit)
|
|
}
|
|
return fmt.Sprintf("unexpected %s while scanning %s expected %s", scanner.TokenString(err.got), err.unit, exp.String())
|
|
}
|