Initial commit
This commit is contained in:
commit
81068376fa
|
@ -0,0 +1,612 @@
|
||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/localhots/penny/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Basics
|
||||||
|
|
||||||
|
type (
|
||||||
|
IoNumber int
|
||||||
|
Word string
|
||||||
|
Attrib interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewWord(word Attrib) (Word, error) {
|
||||||
|
lit := word.(*token.Token).Lit
|
||||||
|
return Word(lit), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIoNumber(number Attrib) (IoNumber, error) {
|
||||||
|
lit := number.(*token.Token).Lit
|
||||||
|
n, err := strconv.Atoi(string(lit))
|
||||||
|
return IoNumber(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assignment
|
||||||
|
|
||||||
|
type (
|
||||||
|
Assignment struct {
|
||||||
|
a, b Word
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAssignment(a, b Attrib) (*Assignment, error) {
|
||||||
|
return &Assignment{a.(Word), b.(Word)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List
|
||||||
|
|
||||||
|
type (
|
||||||
|
List []*AndOr
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewList(ao Attrib) (List, error) {
|
||||||
|
return List{ao.(*AndOr)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendToList(list Attrib, el Attrib) (List, error) {
|
||||||
|
l := list.(List)
|
||||||
|
l = append(l, el.(*AndOr))
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AndOr
|
||||||
|
|
||||||
|
type (
|
||||||
|
Logic int
|
||||||
|
AndOrStmt []*AndOr
|
||||||
|
AndOr struct {
|
||||||
|
op Logic
|
||||||
|
p *Pipeline
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
L_FIRST Logic = iota
|
||||||
|
L_AND
|
||||||
|
L_OR
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAndOr(p Attrib) (AndOrStmt, error) {
|
||||||
|
return AndOrStmt{&AndOr{L_FIRST, p.(*Pipeline)}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendAnd(stmt Attrib, p Attrib) (AndOrStmt, error) {
|
||||||
|
pp := stmt.(AndOrStmt)
|
||||||
|
pp = append(pp, &AndOr{L_AND, p.(*Pipeline)})
|
||||||
|
return pp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendOr(stmt Attrib, p Attrib) (AndOrStmt, error) {
|
||||||
|
pp := stmt.(AndOrStmt)
|
||||||
|
pp = append(pp, &AndOr{L_OR, p.(*Pipeline)})
|
||||||
|
return pp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipeline
|
||||||
|
|
||||||
|
type (
|
||||||
|
Pipeline struct {
|
||||||
|
seq PipeSequence
|
||||||
|
inverse bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPipeline(seq Attrib, inverse bool) (*Pipeline, error) {
|
||||||
|
return &Pipeline{seq.(PipeSequence), inverse}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeSequence
|
||||||
|
|
||||||
|
type (
|
||||||
|
PipeSequence []Command
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPipeSequence(cmd Attrib) (PipeSequence, error) {
|
||||||
|
return PipeSequence{cmd.(*Command)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendToPipeSequence(ps Attrib, cmd Attrib) (PipeSequence, error) {
|
||||||
|
ps1 := ps.(PipeSequence)
|
||||||
|
ps1 = append(ps1, cmd.(Command))
|
||||||
|
return ps1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command
|
||||||
|
|
||||||
|
type (
|
||||||
|
Command interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCommand(cmd Attrib, rl Attrib) (Command, error) {
|
||||||
|
return Command(cmd), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompoundCommand
|
||||||
|
|
||||||
|
type (
|
||||||
|
CompoundCommand interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCompoundCommand(cmd Attrib) (CompoundCommand, error) {
|
||||||
|
return CompoundCommand(cmd), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subshell
|
||||||
|
|
||||||
|
type (
|
||||||
|
Subshell struct {
|
||||||
|
cc CompoundCommand
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSubshell(cc Attrib) (*Subshell, error) {
|
||||||
|
return &Subshell{cc.(CompoundCommand)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompoundList
|
||||||
|
|
||||||
|
type (
|
||||||
|
CompoundList struct {
|
||||||
|
term *Term
|
||||||
|
sep *Separator
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCompoundList(term Attrib, sep Attrib) (*CompoundList, error) {
|
||||||
|
return &CompoundList{term.(*Term), sep.(*Separator)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Term
|
||||||
|
|
||||||
|
type (
|
||||||
|
Term []*TermItem
|
||||||
|
TermItem struct {
|
||||||
|
ao AndOrStmt
|
||||||
|
sep *Separator
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTerm(ao Attrib) (Term, error) {
|
||||||
|
return Term{&TermItem{ao.(AndOrStmt), nil}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendToTerm(term Attrib, ao Attrib, sep Attrib) (Term, error) {
|
||||||
|
t := term.(Term)
|
||||||
|
t = append(t, &TermItem{ao.(AndOrStmt), sep.(*Separator)})
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForClause
|
||||||
|
|
||||||
|
type (
|
||||||
|
ForClause struct {
|
||||||
|
name Name
|
||||||
|
wl Wordlist
|
||||||
|
dg *DoGroup
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewForClause(name Attrib, wl Attrib, dg Attrib) (*ForClause, error) {
|
||||||
|
return &ForClause{
|
||||||
|
name: name.(Name),
|
||||||
|
wl: wl.(Wordlist),
|
||||||
|
dg: dg.(*DoGroup),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name
|
||||||
|
|
||||||
|
type (
|
||||||
|
Name Word
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewName(w Attrib) (Name, error) {
|
||||||
|
return Name(w.(Word)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wordlist
|
||||||
|
|
||||||
|
type (
|
||||||
|
Wordlist []Word
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewWordlist(w Attrib) (Wordlist, error) {
|
||||||
|
return Wordlist{w.(Word)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendToWordlist(wl Attrib, w Attrib) (Wordlist, error) {
|
||||||
|
wl1 := wl.(Wordlist)
|
||||||
|
wl1 = append(wl1, w.(Word))
|
||||||
|
return wl1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaseClause
|
||||||
|
|
||||||
|
type (
|
||||||
|
CaseClause struct {
|
||||||
|
word Word
|
||||||
|
cl CaseList
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCaseClause(word Attrib, cl Attrib) (*CaseClause, error) {
|
||||||
|
return &CaseClause{
|
||||||
|
word: word.(Word),
|
||||||
|
cl: cl.(CaseList),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaseListNs
|
||||||
|
// CaseList
|
||||||
|
|
||||||
|
type (
|
||||||
|
CaseList []*CaseItem
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCaseList(ci Attrib) (CaseList, error) {
|
||||||
|
return CaseList{ci.(*CaseItem)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendToCaseList(cl Attrib, ci Attrib) (CaseList, error) {
|
||||||
|
cl1 := cl.(CaseList)
|
||||||
|
cl1 = append(cl1, ci.(*CaseItem))
|
||||||
|
return cl1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaseItemNs
|
||||||
|
// CaseItem
|
||||||
|
|
||||||
|
type (
|
||||||
|
CaseItem struct {
|
||||||
|
p Pattern
|
||||||
|
cl *CompoundList
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCaseItem(p Attrib, cl Attrib) (*CaseItem, error) {
|
||||||
|
return &CaseItem{
|
||||||
|
p: p.(Pattern),
|
||||||
|
cl: cl.(*CompoundList),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern
|
||||||
|
|
||||||
|
type (
|
||||||
|
Pattern []Word
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPattern(w Attrib) (Pattern, error) {
|
||||||
|
return Pattern{w.(Word)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendToPattern(p Attrib, w Attrib) (Pattern, error) {
|
||||||
|
p1 := p.(Pattern)
|
||||||
|
p1 = append(p1, w.(Word))
|
||||||
|
return p1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfClause
|
||||||
|
// ElsePart
|
||||||
|
|
||||||
|
type (
|
||||||
|
IfClause struct {
|
||||||
|
cond *CompoundList
|
||||||
|
action *CompoundList
|
||||||
|
elsep *IfClause
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewIfClause(cond Attrib, action Attrib, elsep Attrib) (*IfClause, error) {
|
||||||
|
return &IfClause{
|
||||||
|
cond: cond.(*CompoundList),
|
||||||
|
action: action.(*CompoundList),
|
||||||
|
elsep: elsep.(*IfClause),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhileClause
|
||||||
|
|
||||||
|
type (
|
||||||
|
WhileClause struct {
|
||||||
|
cond *CompoundList
|
||||||
|
dg *DoGroup
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewWhileClause(cond Attrib, dg Attrib) (*WhileClause, error) {
|
||||||
|
return &WhileClause{
|
||||||
|
cond: cond.(*CompoundList),
|
||||||
|
dg: dg.(*DoGroup),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UntilClause
|
||||||
|
|
||||||
|
type (
|
||||||
|
UntilClause struct {
|
||||||
|
cond *CompoundList
|
||||||
|
dg *DoGroup
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewUntilClause(cond Attrib, dg Attrib) (*UntilClause, error) {
|
||||||
|
return &UntilClause{
|
||||||
|
cond: cond.(*CompoundList),
|
||||||
|
dg: dg.(*DoGroup),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FunctionDefinition
|
||||||
|
|
||||||
|
type (
|
||||||
|
FunctionDefinition struct {
|
||||||
|
name FunctionName
|
||||||
|
body *FunctionBody
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFunctionDefinition(name Attrib, body Attrib) (*FunctionDefinition, error) {
|
||||||
|
return &FunctionDefinition{
|
||||||
|
name: name.(FunctionName),
|
||||||
|
body: body.(*FunctionBody),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FunctionBody
|
||||||
|
|
||||||
|
type (
|
||||||
|
FunctionBody struct {
|
||||||
|
cc CompoundCommand
|
||||||
|
rl RedirectList
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFunctionBody(cc Attrib, rl Attrib) (*FunctionBody, error) {
|
||||||
|
return &FunctionBody{
|
||||||
|
cc: cc.(CompoundCommand),
|
||||||
|
rl: rl.(RedirectList),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FunctionName
|
||||||
|
|
||||||
|
type (
|
||||||
|
FunctionName Word
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFunctionName(w Attrib) (FunctionName, error) {
|
||||||
|
return FunctionName(w.(Word)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BraceGroup
|
||||||
|
|
||||||
|
type (
|
||||||
|
BraceGroup struct {
|
||||||
|
cl *CompoundList
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewBraceGroup(cl Attrib) (*BraceGroup, error) {
|
||||||
|
return &BraceGroup{
|
||||||
|
cl: cl.(*CompoundList),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoGroup
|
||||||
|
|
||||||
|
type (
|
||||||
|
DoGroup struct {
|
||||||
|
cl *CompoundList
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDoGroup(cl Attrib) (*DoGroup, error) {
|
||||||
|
return &DoGroup{cl.(*CompoundList)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleCommand
|
||||||
|
|
||||||
|
type (
|
||||||
|
SimpleCommand struct {
|
||||||
|
prefix *CmdPrefix
|
||||||
|
name Name
|
||||||
|
word Word
|
||||||
|
suffix *CmdSuffix
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSimpleCommand(prefix, name, word, suffix Attrib) (*SimpleCommand, error) {
|
||||||
|
return &SimpleCommand{
|
||||||
|
prefix: prefix.(*CmdPrefix),
|
||||||
|
name: name.(Name),
|
||||||
|
word: word.(Word),
|
||||||
|
suffix: suffix.(*CmdSuffix),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdName
|
||||||
|
|
||||||
|
type (
|
||||||
|
CmdName Word
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCmdName(w Attrib) (CmdName, error) {
|
||||||
|
return CmdName(w.(Word)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdWord
|
||||||
|
|
||||||
|
type (
|
||||||
|
CmdWord Word
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCmdWord(w Attrib) (CmdWord, error) {
|
||||||
|
return CmdWord(w.(Word)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdPrefix
|
||||||
|
|
||||||
|
type (
|
||||||
|
CmdPrefix struct {
|
||||||
|
assign *Assignment
|
||||||
|
redir *IoRedirect
|
||||||
|
prefix *CmdPrefix
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCmdPrefix(assign, redir, prefix Attrib) (*CmdPrefix, error) {
|
||||||
|
return &CmdPrefix{
|
||||||
|
assign: assign.(*Assignment),
|
||||||
|
redir: assign.(*IoRedirect),
|
||||||
|
prefix: prefix.(*CmdPrefix),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdSuffix
|
||||||
|
|
||||||
|
type (
|
||||||
|
CmdSuffix struct {
|
||||||
|
word Word
|
||||||
|
redir *IoRedirect
|
||||||
|
suffix *CmdSuffix
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCmdSuffix(word, redir, suffix Attrib) (*CmdSuffix, error) {
|
||||||
|
return &CmdSuffix{
|
||||||
|
word: word.(Word),
|
||||||
|
redir: redir.(*IoRedirect),
|
||||||
|
suffix: suffix.(*CmdSuffix),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedirectList
|
||||||
|
|
||||||
|
type (
|
||||||
|
RedirectList []*IoRedirect
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRedirectList(redir Attrib) (RedirectList, error) {
|
||||||
|
return RedirectList{redir.(*IoRedirect)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendToRedirectList(list Attrib, el Attrib) (RedirectList, error) {
|
||||||
|
l := list.(RedirectList)
|
||||||
|
l = append(l, el.(*IoRedirect))
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoRedirect
|
||||||
|
|
||||||
|
type (
|
||||||
|
IoRedirect struct {
|
||||||
|
file *IoFile
|
||||||
|
num IoNumber
|
||||||
|
here *IoHere
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewIoRedirect(file Attrib, num Attrib, here Attrib) (*IoRedirect, error) {
|
||||||
|
return &IoRedirect{
|
||||||
|
file: file.(*IoFile),
|
||||||
|
num: num.(IoNumber),
|
||||||
|
here: here.(*IoHere),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoFile
|
||||||
|
|
||||||
|
type (
|
||||||
|
Redirection int
|
||||||
|
IoFile struct {
|
||||||
|
file Filename
|
||||||
|
redir Redirection
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
R_STDIN Redirection = iota
|
||||||
|
R_INFD
|
||||||
|
R_STDOUT
|
||||||
|
R_OUTFD
|
||||||
|
R_APPEND
|
||||||
|
R_ORWFD
|
||||||
|
R_OUTSP
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewIoFile(file Attrib, redir Redirection) (*IoFile, error) {
|
||||||
|
return &IoFile{
|
||||||
|
file: file.(Filename),
|
||||||
|
redir: redir,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filename
|
||||||
|
|
||||||
|
type (
|
||||||
|
Filename Word
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFilename(w Attrib) (Filename, error) {
|
||||||
|
return Filename(w.(Word)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IoHere
|
||||||
|
|
||||||
|
type (
|
||||||
|
IoHere struct {
|
||||||
|
word Word
|
||||||
|
suppressTabs bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewIoHere(w Attrib, st bool) (*IoHere, error) {
|
||||||
|
return &IoHere{
|
||||||
|
word: w.(Word),
|
||||||
|
suppressTabs: st,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HereEnd
|
||||||
|
|
||||||
|
type (
|
||||||
|
HereEnd Word
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewHereEnd(w Attrib) (HereEnd, error) {
|
||||||
|
return HereEnd(w.(Word)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewlineList
|
||||||
|
|
||||||
|
// Linebreak
|
||||||
|
|
||||||
|
// SeparatorOp
|
||||||
|
|
||||||
|
type (
|
||||||
|
SeparatorOp int
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
S_AMP SeparatorOp = iota
|
||||||
|
S_SEMICOLON
|
||||||
|
)
|
||||||
|
|
||||||
|
// Separartor
|
||||||
|
|
||||||
|
type (
|
||||||
|
Separator struct {
|
||||||
|
s SeparatorOp
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSeparator(op Attrib) (*Separator, error) {
|
||||||
|
return &Separator{op.(SeparatorOp)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SequentialSep
|
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import(
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/localhots/penny/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorSymbol interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Err error
|
||||||
|
ErrorToken *token.Token
|
||||||
|
ErrorSymbols []ErrorSymbol
|
||||||
|
ExpectedTokens []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (E *Error) String() string {
|
||||||
|
w := new(bytes.Buffer)
|
||||||
|
fmt.Fprintf(w, "Error")
|
||||||
|
if E.Err != nil {
|
||||||
|
fmt.Fprintf(w, " %s\n", E.Err)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "\n")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "Token: type=%d, lit=%s\n", E.ErrorToken.Type, E.ErrorToken.Lit)
|
||||||
|
fmt.Fprintf(w, "Pos: offset=%d, line=%d, column=%d\n", E.ErrorToken.Pos.Offset, E.ErrorToken.Pos.Line, E.ErrorToken.Pos.Column)
|
||||||
|
fmt.Fprintf(w, "Expected one of: ")
|
||||||
|
for _, sym := range E.ExpectedTokens {
|
||||||
|
fmt.Fprintf(w, "%s ", sym)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "ErrorSymbol:\n")
|
||||||
|
for _, sym := range E.ErrorSymbols {
|
||||||
|
fmt.Fprintf(w, "%v\n", sym)
|
||||||
|
}
|
||||||
|
return w.String()
|
||||||
|
}
|
|
@ -0,0 +1,314 @@
|
||||||
|
|
||||||
|
package lexer
|
||||||
|
|
||||||
|
import(
|
||||||
|
"fmt"
|
||||||
|
"github.com/localhots/penny/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActionTable [NumStates] ActionRow
|
||||||
|
|
||||||
|
type ActionRow struct {
|
||||||
|
Accept token.Type
|
||||||
|
Ignore string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this ActionRow) String() string {
|
||||||
|
return fmt.Sprintf("Accept=%d, Ignore=%s", this.Accept, this.Ignore)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ActTab = ActionTable{
|
||||||
|
ActionRow{ // S0
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S1
|
||||||
|
Accept: -1,
|
||||||
|
Ignore: "!whitespace",
|
||||||
|
},
|
||||||
|
ActionRow{ // S2
|
||||||
|
Accept: 7,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S3
|
||||||
|
Accept: 39,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S4
|
||||||
|
Accept: 9,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S5
|
||||||
|
Accept: 10,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S6
|
||||||
|
Accept: 3,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S7
|
||||||
|
Accept: 40,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S8
|
||||||
|
Accept: 28,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S9
|
||||||
|
Accept: 4,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S10
|
||||||
|
Accept: 30,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S11
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S12
|
||||||
|
Accept: 13,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S13
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S14
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S15
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S16
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S17
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S18
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S19
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S20
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S21
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S22
|
||||||
|
Accept: 24,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S23
|
||||||
|
Accept: 8,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S24
|
||||||
|
Accept: 25,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S25
|
||||||
|
Accept: 38,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S26
|
||||||
|
Accept: 5,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S27
|
||||||
|
Accept: 16,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S28
|
||||||
|
Accept: 29,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S29
|
||||||
|
Accept: 35,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S30
|
||||||
|
Accept: 33,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S31
|
||||||
|
Accept: 31,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S32
|
||||||
|
Accept: 32,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S33
|
||||||
|
Accept: 34,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S34
|
||||||
|
Accept: 37,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S35
|
||||||
|
Accept: 13,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S36
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S37
|
||||||
|
Accept: 2,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S38
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S39
|
||||||
|
Accept: 26,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S40
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S41
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S42
|
||||||
|
Accept: 19,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S43
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S44
|
||||||
|
Accept: 17,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S45
|
||||||
|
Accept: 12,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S46
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S47
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S48
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S49
|
||||||
|
Accept: 6,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S50
|
||||||
|
Accept: 36,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S51
|
||||||
|
Accept: 13,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S52
|
||||||
|
Accept: 2,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S53
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S54
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S55
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S56
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S57
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S58
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S59
|
||||||
|
Accept: 11,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S60
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S61
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S62
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S63
|
||||||
|
Accept: 14,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S64
|
||||||
|
Accept: 27,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S65
|
||||||
|
Accept: 20,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S66
|
||||||
|
Accept: 21,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S67
|
||||||
|
Accept: 15,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S68
|
||||||
|
Accept: 18,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S69
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S70
|
||||||
|
Accept: 0,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S71
|
||||||
|
Accept: 23,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
ActionRow{ // S72
|
||||||
|
Accept: 22,
|
||||||
|
Ignore: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,233 @@
|
||||||
|
|
||||||
|
package lexer
|
||||||
|
|
||||||
|
import (
|
||||||
|
|
||||||
|
// "fmt"
|
||||||
|
// "github.com/localhots/penny/util"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
"unicode/utf8"
|
||||||
|
"github.com/localhots/penny/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
const(
|
||||||
|
NoState = -1
|
||||||
|
NumStates = 73
|
||||||
|
NumSymbols = 90
|
||||||
|
)
|
||||||
|
|
||||||
|
type Lexer struct {
|
||||||
|
src []byte
|
||||||
|
pos int
|
||||||
|
line int
|
||||||
|
column int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLexer(src []byte) *Lexer {
|
||||||
|
lexer := &Lexer{
|
||||||
|
src: src,
|
||||||
|
pos: 0,
|
||||||
|
line: 1,
|
||||||
|
column: 1,
|
||||||
|
}
|
||||||
|
return lexer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLexerFile(fpath string) (*Lexer, error) {
|
||||||
|
src, err := ioutil.ReadFile(fpath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewLexer(src), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Lexer) Scan() (tok *token.Token) {
|
||||||
|
|
||||||
|
// fmt.Printf("Lexer.Scan() pos=%d\n", this.pos)
|
||||||
|
|
||||||
|
tok = new(token.Token)
|
||||||
|
if this.pos >= len(this.src) {
|
||||||
|
tok.Type = token.EOF
|
||||||
|
tok.Pos.Offset, tok.Pos.Line, tok.Pos.Column = this.pos, this.line, this.column
|
||||||
|
return
|
||||||
|
}
|
||||||
|
start, end := this.pos, 0
|
||||||
|
tok.Type = token.INVALID
|
||||||
|
state, rune1, size := 0, rune(-1), 0
|
||||||
|
for state != -1 {
|
||||||
|
|
||||||
|
// fmt.Printf("\tpos=%d, line=%d, col=%d, state=%d\n", this.pos, this.line, this.column, state)
|
||||||
|
|
||||||
|
if this.pos >= len(this.src) {
|
||||||
|
rune1 = -1
|
||||||
|
} else {
|
||||||
|
rune1, size = utf8.DecodeRune(this.src[this.pos:])
|
||||||
|
this.pos += size
|
||||||
|
}
|
||||||
|
switch rune1 {
|
||||||
|
case '\n':
|
||||||
|
this.line++
|
||||||
|
this.column = 1
|
||||||
|
case '\r':
|
||||||
|
this.column = 1
|
||||||
|
case '\t':
|
||||||
|
this.column += 4
|
||||||
|
default:
|
||||||
|
this.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Production start
|
||||||
|
if rune1 != -1 {
|
||||||
|
state = TransTab[state](rune1)
|
||||||
|
} else {
|
||||||
|
state = -1
|
||||||
|
}
|
||||||
|
// Production end
|
||||||
|
|
||||||
|
// Debug start
|
||||||
|
// nextState := -1
|
||||||
|
// if rune1 != -1 {
|
||||||
|
// nextState = TransTab[state](rune1)
|
||||||
|
// }
|
||||||
|
// fmt.Printf("\tS%d, : tok=%s, rune == %s(%x), next state == %d\n", state, token.TokMap.Id(tok.Type), util.RuneToString(rune1), rune1, nextState)
|
||||||
|
// fmt.Printf("\t\tpos=%d, size=%d, start=%d, end=%d\n", this.pos, size, start, end)
|
||||||
|
// if nextState != -1 {
|
||||||
|
// fmt.Printf("\t\taction:%s\n", ActTab[nextState].String())
|
||||||
|
// }
|
||||||
|
// state = nextState
|
||||||
|
// Debug end
|
||||||
|
|
||||||
|
|
||||||
|
if state != -1 {
|
||||||
|
switch {
|
||||||
|
case ActTab[state].Accept != -1:
|
||||||
|
tok.Type = ActTab[state].Accept
|
||||||
|
// fmt.Printf("\t Accept(%s), %s(%d)\n", string(act), token.TokMap.Id(tok), tok)
|
||||||
|
end = this.pos
|
||||||
|
case ActTab[state].Ignore != "":
|
||||||
|
// fmt.Printf("\t Ignore(%s)\n", string(act))
|
||||||
|
start = this.pos
|
||||||
|
state = 0
|
||||||
|
if start >= len(this.src) {
|
||||||
|
tok.Type = token.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if tok.Type == token.INVALID {
|
||||||
|
end = this.pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if end > start {
|
||||||
|
this.pos = end
|
||||||
|
tok.Lit = this.src[start:end]
|
||||||
|
} else {
|
||||||
|
tok.Lit = []byte{}
|
||||||
|
}
|
||||||
|
tok.Pos.Offset = start
|
||||||
|
tok.Pos.Column = this.column
|
||||||
|
tok.Pos.Line = this.line
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Lexer) Reset() {
|
||||||
|
this.pos = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Lexer symbols:
|
||||||
|
0: '_'
|
||||||
|
1: '_'
|
||||||
|
2: '_'
|
||||||
|
3: '='
|
||||||
|
4: '&'
|
||||||
|
5: '&'
|
||||||
|
6: '|'
|
||||||
|
7: '|'
|
||||||
|
8: '!'
|
||||||
|
9: '|'
|
||||||
|
10: '('
|
||||||
|
11: ')'
|
||||||
|
12: 'f'
|
||||||
|
13: 'o'
|
||||||
|
14: 'r'
|
||||||
|
15: 'i'
|
||||||
|
16: 'n'
|
||||||
|
17: 'c'
|
||||||
|
18: 'a'
|
||||||
|
19: 's'
|
||||||
|
20: 'e'
|
||||||
|
21: 'e'
|
||||||
|
22: 's'
|
||||||
|
23: 'a'
|
||||||
|
24: 'c'
|
||||||
|
25: ';'
|
||||||
|
26: ';'
|
||||||
|
27: 'i'
|
||||||
|
28: 'f'
|
||||||
|
29: 't'
|
||||||
|
30: 'h'
|
||||||
|
31: 'e'
|
||||||
|
32: 'n'
|
||||||
|
33: 'f'
|
||||||
|
34: 'i'
|
||||||
|
35: 'e'
|
||||||
|
36: 'l'
|
||||||
|
37: 'i'
|
||||||
|
38: 'f'
|
||||||
|
39: 'e'
|
||||||
|
40: 'l'
|
||||||
|
41: 's'
|
||||||
|
42: 'e'
|
||||||
|
43: 'w'
|
||||||
|
44: 'h'
|
||||||
|
45: 'i'
|
||||||
|
46: 'l'
|
||||||
|
47: 'e'
|
||||||
|
48: 'u'
|
||||||
|
49: 'n'
|
||||||
|
50: 't'
|
||||||
|
51: 'i'
|
||||||
|
52: 'l'
|
||||||
|
53: '{'
|
||||||
|
54: '}'
|
||||||
|
55: 'd'
|
||||||
|
56: 'o'
|
||||||
|
57: 'd'
|
||||||
|
58: 'o'
|
||||||
|
59: 'n'
|
||||||
|
60: 'e'
|
||||||
|
61: '<'
|
||||||
|
62: '<'
|
||||||
|
63: '&'
|
||||||
|
64: '>'
|
||||||
|
65: '>'
|
||||||
|
66: '&'
|
||||||
|
67: '>'
|
||||||
|
68: '>'
|
||||||
|
69: '<'
|
||||||
|
70: '>'
|
||||||
|
71: '>'
|
||||||
|
72: '|'
|
||||||
|
73: '<'
|
||||||
|
74: '<'
|
||||||
|
75: '<'
|
||||||
|
76: '<'
|
||||||
|
77: '-'
|
||||||
|
78: '\'
|
||||||
|
79: 'n'
|
||||||
|
80: '&'
|
||||||
|
81: ';'
|
||||||
|
82: ' '
|
||||||
|
83: '\t'
|
||||||
|
84: '\n'
|
||||||
|
85: '\r'
|
||||||
|
86: 'a'-'z'
|
||||||
|
87: 'A'-'Z'
|
||||||
|
88: '0'-'9'
|
||||||
|
89: .
|
||||||
|
|
||||||
|
*/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,50 @@
|
||||||
|
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type action interface {
|
||||||
|
act()
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
accept bool
|
||||||
|
shift int // value is next state index
|
||||||
|
reduce int // value is production index
|
||||||
|
)
|
||||||
|
|
||||||
|
func (this accept) act() {}
|
||||||
|
func (this shift) act() {}
|
||||||
|
func (this reduce) act() {}
|
||||||
|
|
||||||
|
func (this accept) Equal(that action) bool {
|
||||||
|
if _, ok := that.(accept); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this reduce) Equal(that action) bool {
|
||||||
|
that1, ok := that.(reduce)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return this == that1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this shift) Equal(that action) bool {
|
||||||
|
that1, ok := that.(shift)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return this == that1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this accept) String() string { return "accept(0)" }
|
||||||
|
func (this shift) String() string { return fmt.Sprintf("shift:%d", this) }
|
||||||
|
func (this reduce) String() string {
|
||||||
|
return fmt.Sprintf("reduce:%d(%s)", this, productionsTable[this].String)
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
|
||||||
|
package parser
|
||||||
|
|
||||||
|
type(
|
||||||
|
actionTable [numStates]actionRow
|
||||||
|
actionRow struct {
|
||||||
|
canRecover bool
|
||||||
|
actions [numSymbols]action
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var actionTab = actionTable{
|
||||||
|
actionRow{ // S0
|
||||||
|
canRecover: false,
|
||||||
|
actions: [numSymbols]action{
|
||||||
|
nil, /* INVALID */
|
||||||
|
nil, /* $ */
|
||||||
|
shift(2), /* word */
|
||||||
|
nil, /* number */
|
||||||
|
nil, /* = */
|
||||||
|
nil, /* && */
|
||||||
|
nil, /* || */
|
||||||
|
nil, /* ! */
|
||||||
|
nil, /* | */
|
||||||
|
nil, /* ( */
|
||||||
|
nil, /* ) */
|
||||||
|
nil, /* for */
|
||||||
|
nil, /* in */
|
||||||
|
nil, /* name */
|
||||||
|
nil, /* case */
|
||||||
|
nil, /* esac */
|
||||||
|
nil, /* ;; */
|
||||||
|
nil, /* if */
|
||||||
|
nil, /* then */
|
||||||
|
nil, /* fi */
|
||||||
|
nil, /* elif */
|
||||||
|
nil, /* else */
|
||||||
|
nil, /* while */
|
||||||
|
nil, /* until */
|
||||||
|
nil, /* { */
|
||||||
|
nil, /* } */
|
||||||
|
nil, /* do */
|
||||||
|
nil, /* done */
|
||||||
|
nil, /* < */
|
||||||
|
nil, /* <& */
|
||||||
|
nil, /* > */
|
||||||
|
nil, /* >& */
|
||||||
|
nil, /* >> */
|
||||||
|
nil, /* <> */
|
||||||
|
nil, /* >| */
|
||||||
|
nil, /* << */
|
||||||
|
nil, /* <<- */
|
||||||
|
nil, /* \n */
|
||||||
|
nil, /* nothing */
|
||||||
|
nil, /* & */
|
||||||
|
nil, /* ; */
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
actionRow{ // S1
|
||||||
|
canRecover: false,
|
||||||
|
actions: [numSymbols]action{
|
||||||
|
nil, /* INVALID */
|
||||||
|
accept(true), /* $ */
|
||||||
|
nil, /* word */
|
||||||
|
nil, /* number */
|
||||||
|
nil, /* = */
|
||||||
|
nil, /* && */
|
||||||
|
nil, /* || */
|
||||||
|
nil, /* ! */
|
||||||
|
nil, /* | */
|
||||||
|
nil, /* ( */
|
||||||
|
nil, /* ) */
|
||||||
|
nil, /* for */
|
||||||
|
nil, /* in */
|
||||||
|
nil, /* name */
|
||||||
|
nil, /* case */
|
||||||
|
nil, /* esac */
|
||||||
|
nil, /* ;; */
|
||||||
|
nil, /* if */
|
||||||
|
nil, /* then */
|
||||||
|
nil, /* fi */
|
||||||
|
nil, /* elif */
|
||||||
|
nil, /* else */
|
||||||
|
nil, /* while */
|
||||||
|
nil, /* until */
|
||||||
|
nil, /* { */
|
||||||
|
nil, /* } */
|
||||||
|
nil, /* do */
|
||||||
|
nil, /* done */
|
||||||
|
nil, /* < */
|
||||||
|
nil, /* <& */
|
||||||
|
nil, /* > */
|
||||||
|
nil, /* >& */
|
||||||
|
nil, /* >> */
|
||||||
|
nil, /* <> */
|
||||||
|
nil, /* >| */
|
||||||
|
nil, /* << */
|
||||||
|
nil, /* <<- */
|
||||||
|
nil, /* \n */
|
||||||
|
nil, /* nothing */
|
||||||
|
nil, /* & */
|
||||||
|
nil, /* ; */
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
actionRow{ // S2
|
||||||
|
canRecover: false,
|
||||||
|
actions: [numSymbols]action{
|
||||||
|
nil, /* INVALID */
|
||||||
|
reduce(1), /* $, reduce: Word */
|
||||||
|
nil, /* word */
|
||||||
|
nil, /* number */
|
||||||
|
nil, /* = */
|
||||||
|
nil, /* && */
|
||||||
|
nil, /* || */
|
||||||
|
nil, /* ! */
|
||||||
|
nil, /* | */
|
||||||
|
nil, /* ( */
|
||||||
|
nil, /* ) */
|
||||||
|
nil, /* for */
|
||||||
|
nil, /* in */
|
||||||
|
nil, /* name */
|
||||||
|
nil, /* case */
|
||||||
|
nil, /* esac */
|
||||||
|
nil, /* ;; */
|
||||||
|
nil, /* if */
|
||||||
|
nil, /* then */
|
||||||
|
nil, /* fi */
|
||||||
|
nil, /* elif */
|
||||||
|
nil, /* else */
|
||||||
|
nil, /* while */
|
||||||
|
nil, /* until */
|
||||||
|
nil, /* { */
|
||||||
|
nil, /* } */
|
||||||
|
nil, /* do */
|
||||||
|
nil, /* done */
|
||||||
|
nil, /* < */
|
||||||
|
nil, /* <& */
|
||||||
|
nil, /* > */
|
||||||
|
nil, /* >& */
|
||||||
|
nil, /* >> */
|
||||||
|
nil, /* <> */
|
||||||
|
nil, /* >| */
|
||||||
|
nil, /* << */
|
||||||
|
nil, /* <<- */
|
||||||
|
nil, /* \n */
|
||||||
|
nil, /* nothing */
|
||||||
|
nil, /* & */
|
||||||
|
nil, /* ; */
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
*/
|
||||||
|
package parser
|
||||||
|
|
||||||
|
const numNTSymbols = 47
|
||||||
|
type(
|
||||||
|
gotoTable [numStates]gotoRow
|
||||||
|
gotoRow [numNTSymbols] int
|
||||||
|
)
|
||||||
|
|
||||||
|
var gotoTab = gotoTable{
|
||||||
|
gotoRow{ // S0
|
||||||
|
|
||||||
|
-1, // S'
|
||||||
|
1, // Word
|
||||||
|
-1, // IoNumber
|
||||||
|
-1, // AssignmentWord
|
||||||
|
-1, // List
|
||||||
|
-1, // AndOr
|
||||||
|
-1, // Pipeline
|
||||||
|
-1, // PipeSequence
|
||||||
|
-1, // Command
|
||||||
|
-1, // CompoundCommand
|
||||||
|
-1, // Subshell
|
||||||
|
-1, // CompoundList
|
||||||
|
-1, // Term
|
||||||
|
-1, // ForClause
|
||||||
|
-1, // Name
|
||||||
|
-1, // Wordlist
|
||||||
|
-1, // CaseClause
|
||||||
|
-1, // CaseListNs
|
||||||
|
-1, // CaseList
|
||||||
|
-1, // CaseItemNs
|
||||||
|
-1, // CaseItem
|
||||||
|
-1, // Pattern
|
||||||
|
-1, // IfClause
|
||||||
|
-1, // ElsePart
|
||||||
|
-1, // WhileClause
|
||||||
|
-1, // UntilClause
|
||||||
|
-1, // FunctionDefinition
|
||||||
|
-1, // FunctionBody
|
||||||
|
-1, // FunctionName
|
||||||
|
-1, // BraceGroup
|
||||||
|
-1, // DoGroup
|
||||||
|
-1, // SimpleCommand
|
||||||
|
-1, // CmdName
|
||||||
|
-1, // CmdWord
|
||||||
|
-1, // CmdPrefix
|
||||||
|
-1, // CmdSuffix
|
||||||
|
-1, // RedirectList
|
||||||
|
-1, // IoRedirect
|
||||||
|
-1, // IoFile
|
||||||
|
-1, // Filename
|
||||||
|
-1, // IoHere
|
||||||
|
-1, // HereEnd
|
||||||
|
-1, // NewlineList
|
||||||
|
-1, // Linebreak
|
||||||
|
-1, // SeparatorOp
|
||||||
|
-1, // Separator
|
||||||
|
-1, // SequentialSep
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
gotoRow{ // S1
|
||||||
|
|
||||||
|
-1, // S'
|
||||||
|
-1, // Word
|
||||||
|
-1, // IoNumber
|
||||||
|
-1, // AssignmentWord
|
||||||
|
-1, // List
|
||||||
|
-1, // AndOr
|
||||||
|
-1, // Pipeline
|
||||||
|
-1, // PipeSequence
|
||||||
|
-1, // Command
|
||||||
|
-1, // CompoundCommand
|
||||||
|
-1, // Subshell
|
||||||
|
-1, // CompoundList
|
||||||
|
-1, // Term
|
||||||
|
-1, // ForClause
|
||||||
|
-1, // Name
|
||||||
|
-1, // Wordlist
|
||||||
|
-1, // CaseClause
|
||||||
|
-1, // CaseListNs
|
||||||
|
-1, // CaseList
|
||||||
|
-1, // CaseItemNs
|
||||||
|
-1, // CaseItem
|
||||||
|
-1, // Pattern
|
||||||
|
-1, // IfClause
|
||||||
|
-1, // ElsePart
|
||||||
|
-1, // WhileClause
|
||||||
|
-1, // UntilClause
|
||||||
|
-1, // FunctionDefinition
|
||||||
|
-1, // FunctionBody
|
||||||
|
-1, // FunctionName
|
||||||
|
-1, // BraceGroup
|
||||||
|
-1, // DoGroup
|
||||||
|
-1, // SimpleCommand
|
||||||
|
-1, // CmdName
|
||||||
|
-1, // CmdWord
|
||||||
|
-1, // CmdPrefix
|
||||||
|
-1, // CmdSuffix
|
||||||
|
-1, // RedirectList
|
||||||
|
-1, // IoRedirect
|
||||||
|
-1, // IoFile
|
||||||
|
-1, // Filename
|
||||||
|
-1, // IoHere
|
||||||
|
-1, // HereEnd
|
||||||
|
-1, // NewlineList
|
||||||
|
-1, // Linebreak
|
||||||
|
-1, // SeparatorOp
|
||||||
|
-1, // Separator
|
||||||
|
-1, // SequentialSep
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
gotoRow{ // S2
|
||||||
|
|
||||||
|
-1, // S'
|
||||||
|
-1, // Word
|
||||||
|
-1, // IoNumber
|
||||||
|
-1, // AssignmentWord
|
||||||
|
-1, // List
|
||||||
|
-1, // AndOr
|
||||||
|
-1, // Pipeline
|
||||||
|
-1, // PipeSequence
|
||||||
|
-1, // Command
|
||||||
|
-1, // CompoundCommand
|
||||||
|
-1, // Subshell
|
||||||
|
-1, // CompoundList
|
||||||
|
-1, // Term
|
||||||
|
-1, // ForClause
|
||||||
|
-1, // Name
|
||||||
|
-1, // Wordlist
|
||||||
|
-1, // CaseClause
|
||||||
|
-1, // CaseListNs
|
||||||
|
-1, // CaseList
|
||||||
|
-1, // CaseItemNs
|
||||||
|
-1, // CaseItem
|
||||||
|
-1, // Pattern
|
||||||
|
-1, // IfClause
|
||||||
|
-1, // ElsePart
|
||||||
|
-1, // WhileClause
|
||||||
|
-1, // UntilClause
|
||||||
|
-1, // FunctionDefinition
|
||||||
|
-1, // FunctionBody
|
||||||
|
-1, // FunctionName
|
||||||
|
-1, // BraceGroup
|
||||||
|
-1, // DoGroup
|
||||||
|
-1, // SimpleCommand
|
||||||
|
-1, // CmdName
|
||||||
|
-1, // CmdWord
|
||||||
|
-1, // CmdPrefix
|
||||||
|
-1, // CmdSuffix
|
||||||
|
-1, // RedirectList
|
||||||
|
-1, // IoRedirect
|
||||||
|
-1, // IoFile
|
||||||
|
-1, // Filename
|
||||||
|
-1, // IoHere
|
||||||
|
-1, // HereEnd
|
||||||
|
-1, // NewlineList
|
||||||
|
-1, // Linebreak
|
||||||
|
-1, // SeparatorOp
|
||||||
|
-1, // Separator
|
||||||
|
-1, // SequentialSep
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import(
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"errors"
|
||||||
|
parseError "github.com/localhots/penny/errors"
|
||||||
|
"github.com/localhots/penny/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
numProductions = 108
|
||||||
|
numStates = 3
|
||||||
|
numSymbols = 88
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stack
|
||||||
|
|
||||||
|
type stack struct {
|
||||||
|
state []int
|
||||||
|
attrib []Attrib
|
||||||
|
}
|
||||||
|
|
||||||
|
const iNITIAL_STACK_SIZE = 100
|
||||||
|
|
||||||
|
func newStack() *stack {
|
||||||
|
return &stack{ state: make([]int, 0, iNITIAL_STACK_SIZE),
|
||||||
|
attrib: make([]Attrib, 0, iNITIAL_STACK_SIZE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *stack) reset() {
|
||||||
|
this.state = this.state[0:0]
|
||||||
|
this.attrib = this.attrib[0:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *stack) push(s int, a Attrib) {
|
||||||
|
this.state = append(this.state, s)
|
||||||
|
this.attrib = append(this.attrib, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func(this *stack) top() int {
|
||||||
|
return this.state[len(this.state) - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *stack) peek(pos int) int {
|
||||||
|
return this.state[pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *stack) topIndex() int {
|
||||||
|
return len(this.state) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *stack) popN(items int) []Attrib {
|
||||||
|
lo, hi := len(this.state) - items, len(this.state)
|
||||||
|
|
||||||
|
attrib := this.attrib[lo: hi]
|
||||||
|
|
||||||
|
this.state = this.state[:lo]
|
||||||
|
this.attrib = this.attrib[:lo]
|
||||||
|
|
||||||
|
return attrib
|
||||||
|
}
|
||||||
|
|
||||||
|
func (S *stack) String() string {
|
||||||
|
w := new(bytes.Buffer)
|
||||||
|
fmt.Fprintf(w, "stack:\n")
|
||||||
|
for i, st := range S.state {
|
||||||
|
fmt.Fprintf(w, "\t%d:%d , ", i, st)
|
||||||
|
if S.attrib[i] == nil {
|
||||||
|
fmt.Fprintf(w, "nil")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "%v", S.attrib[i])
|
||||||
|
}
|
||||||
|
w.WriteString("\n")
|
||||||
|
}
|
||||||
|
return w.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
stack *stack
|
||||||
|
nextToken *token.Token
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Scanner interface {
|
||||||
|
Scan() (tok *token.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewParser() *Parser {
|
||||||
|
p := &Parser{stack: newStack()}
|
||||||
|
p.Reset()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (P *Parser) Reset() {
|
||||||
|
P.stack.reset()
|
||||||
|
P.stack.push(0, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (P *Parser) Error(err error, scanner Scanner) (recovered bool, errorAttrib *parseError.Error) {
|
||||||
|
errorAttrib = &parseError.Error{
|
||||||
|
Err: err,
|
||||||
|
ErrorToken: P.nextToken,
|
||||||
|
ErrorSymbols: P.popNonRecoveryStates(),
|
||||||
|
ExpectedTokens: make([]string, 0, 8),
|
||||||
|
}
|
||||||
|
for t, action := range actionTab[P.stack.top()].actions {
|
||||||
|
if action != nil {
|
||||||
|
errorAttrib.ExpectedTokens = append(errorAttrib.ExpectedTokens, token.TokMap.Id(token.Type(t)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if action := actionTab[P.stack.top()].actions[token.TokMap.Type("error")]; action != nil {
|
||||||
|
P.stack.push(int(action.(shift)), errorAttrib) // action can only be shift
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if action := actionTab[P.stack.top()].actions[P.nextToken.Type]; action != nil {
|
||||||
|
recovered = true
|
||||||
|
}
|
||||||
|
for !recovered && P.nextToken.Type != token.EOF {
|
||||||
|
P.nextToken = scanner.Scan()
|
||||||
|
if action := actionTab[P.stack.top()].actions[P.nextToken.Type]; action != nil {
|
||||||
|
recovered = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (P *Parser) popNonRecoveryStates() (removedAttribs []parseError.ErrorSymbol) {
|
||||||
|
if rs, ok := P.firstRecoveryState(); ok {
|
||||||
|
errorSymbols := P.stack.popN(int(P.stack.topIndex() - rs))
|
||||||
|
removedAttribs = make([]parseError.ErrorSymbol, len(errorSymbols))
|
||||||
|
for i, e := range errorSymbols {
|
||||||
|
removedAttribs[i] = e
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
removedAttribs = []parseError.ErrorSymbol{}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// recoveryState points to the highest state on the stack, which can recover
|
||||||
|
func (P *Parser) firstRecoveryState() (recoveryState int, canRecover bool) {
|
||||||
|
recoveryState, canRecover = P.stack.topIndex(), actionTab[P.stack.top()].canRecover
|
||||||
|
for recoveryState > 0 && !canRecover {
|
||||||
|
recoveryState--
|
||||||
|
canRecover = actionTab[P.stack.peek(recoveryState)].canRecover
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (P *Parser) newError(err error) error {
|
||||||
|
w := new(bytes.Buffer)
|
||||||
|
fmt.Fprintf(w, "Error in S%d: %s, %s", P.stack.top(), token.TokMap.TokenString(P.nextToken), P.nextToken.Pos.String())
|
||||||
|
if err != nil {
|
||||||
|
w.WriteString(err.Error())
|
||||||
|
} else {
|
||||||
|
w.WriteString(", expected one of: ")
|
||||||
|
actRow := actionTab[P.stack.top()]
|
||||||
|
for i, t := range actRow.actions {
|
||||||
|
if t != nil {
|
||||||
|
fmt.Fprintf(w, "%s ", token.TokMap.Id(token.Type(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New(w.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Parser) Parse(scanner Scanner) (res interface{}, err error) {
|
||||||
|
this.Reset()
|
||||||
|
this.nextToken = scanner.Scan()
|
||||||
|
for acc := false; !acc; {
|
||||||
|
action := actionTab[this.stack.top()].actions[this.nextToken.Type]
|
||||||
|
if action == nil {
|
||||||
|
if recovered, errAttrib := this.Error(nil, scanner); !recovered {
|
||||||
|
this.nextToken = errAttrib.ErrorToken
|
||||||
|
return nil, this.newError(nil)
|
||||||
|
}
|
||||||
|
if action = actionTab[this.stack.top()].actions[this.nextToken.Type]; action == nil {
|
||||||
|
panic("Error recovery led to invalid action")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Printf("S%d %s %s\n", this.stack.top(), token.TokMap.TokenString(this.nextToken), action.String())
|
||||||
|
|
||||||
|
|
||||||
|
switch act := action.(type) {
|
||||||
|
case accept:
|
||||||
|
res = this.stack.popN(1)[0]
|
||||||
|
acc = true
|
||||||
|
case shift:
|
||||||
|
this.stack.push(int(act), this.nextToken)
|
||||||
|
this.nextToken = scanner.Scan()
|
||||||
|
case reduce:
|
||||||
|
prod := productionsTable[int(act)]
|
||||||
|
attrib, err := prod.ReduceFunc(this.stack.popN(prod.NumSymbols))
|
||||||
|
if err != nil {
|
||||||
|
return nil, this.newError(err)
|
||||||
|
} else {
|
||||||
|
this.stack.push(gotoTab[this.stack.top()][prod.NTType], attrib)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unknown action: " + action.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,223 @@
|
||||||
|
!whitespace : ' ' | '\t' | '\n' | '\r' ;
|
||||||
|
|
||||||
|
_letter : 'a'-'z' 'A'-'Z' ;
|
||||||
|
_digit : '0'-'9' ;
|
||||||
|
|
||||||
|
number : _digit {_digit} ;
|
||||||
|
word /*BAD*/ : _letter {_letter | '_'} ;
|
||||||
|
nothing : . ;
|
||||||
|
|
||||||
|
name : ( _letter | '_' ) { _letter | '_' | _digit } ;
|
||||||
|
|
||||||
|
<< import "github.com/localhots/penny/ast" >>
|
||||||
|
|
||||||
|
/* Tokens */
|
||||||
|
|
||||||
|
Word
|
||||||
|
: word << ast.NewWord($0) >>
|
||||||
|
;
|
||||||
|
IoNumber
|
||||||
|
: number << ast.NewIoNumber($0) >>
|
||||||
|
;
|
||||||
|
|
||||||
|
/* Commands */
|
||||||
|
|
||||||
|
/*
|
||||||
|
CompleteCommand
|
||||||
|
: List Separator
|
||||||
|
| List
|
||||||
|
;
|
||||||
|
*/
|
||||||
|
AssignmentWord
|
||||||
|
: Word "=" Word << ast.NewAssignment($0, $2) >>
|
||||||
|
;
|
||||||
|
List
|
||||||
|
: List SeparatorOp AndOr << ast.AppendToList($0, $2) >>
|
||||||
|
| AndOr << ast.NewList($0) >>
|
||||||
|
;
|
||||||
|
AndOr
|
||||||
|
: Pipeline << ast.NewAndOr($0) >>
|
||||||
|
| AndOr "&&" Linebreak Pipeline << ast.AppendAnd($0, $3) >>
|
||||||
|
| AndOr "||" Linebreak Pipeline << ast.AppendOr($0, $3) >>
|
||||||
|
;
|
||||||
|
Pipeline
|
||||||
|
: PipeSequence << ast.NewPipeline($1, false) >>
|
||||||
|
| "!" PipeSequence << ast.NewPipeline($1, true) >>
|
||||||
|
;
|
||||||
|
PipeSequence
|
||||||
|
: Command << ast.NewPipeSequence($0) >>
|
||||||
|
| PipeSequence "|" Linebreak Command << ast.AppendToPipeSequence($0, $3) >>
|
||||||
|
;
|
||||||
|
Command
|
||||||
|
: SimpleCommand << ast.NewCommand($0, nil) >>
|
||||||
|
| CompoundCommand << ast.NewCommand($0, nil) >>
|
||||||
|
| CompoundCommand RedirectList << ast.NewCommand($0, $1) >>
|
||||||
|
| FunctionDefinition << ast.NewCommand($0, nil) >>
|
||||||
|
;
|
||||||
|
CompoundCommand
|
||||||
|
: BraceGroup << ast.NewCompoundCommand($0) >>
|
||||||
|
| Subshell << ast.NewCompoundCommand($0) >>
|
||||||
|
| ForClause << ast.NewCompoundCommand($0) >>
|
||||||
|
| CaseClause << ast.NewCompoundCommand($0) >>
|
||||||
|
| IfClause << ast.NewCompoundCommand($0) >>
|
||||||
|
| WhileClause << ast.NewCompoundCommand($0) >>
|
||||||
|
| UntilClause << ast.NewCompoundCommand($0) >>
|
||||||
|
;
|
||||||
|
Subshell
|
||||||
|
: "(" CompoundList ")" << ast.NewSubshell($1) >>
|
||||||
|
;
|
||||||
|
CompoundList
|
||||||
|
: Term << ast.NewCompoundList($0, nil) >>
|
||||||
|
| NewlineList Term << ast.NewCompoundList($1, $2) >>
|
||||||
|
| Term Separator << ast.NewCompoundList($0, $1) >>
|
||||||
|
| NewlineList Term Separator << ast.NewCompoundList($1, $2) >>
|
||||||
|
;
|
||||||
|
Term
|
||||||
|
: Term Separator AndOr << ast.AppendToTerm($0, $2, $1) >>
|
||||||
|
| AndOr << ast.NewTerm($0) >>
|
||||||
|
;
|
||||||
|
ForClause
|
||||||
|
: "for" Name Linebreak DoGroup << ast.NewForClause($1, ast.Wordlist{}, $3) >>
|
||||||
|
| "for" Name Linebreak "in" SequentialSep DoGroup << ast.NewForClause($1, ast.Wordlist{}, $5) >>
|
||||||
|
| "for" Name Linebreak "in" Wordlist SequentialSep DoGroup << ast.NewForClause($1, $4, $6) >>
|
||||||
|
;
|
||||||
|
Name
|
||||||
|
: name << ast.NewName($0) >> /* Apply rule 5 */
|
||||||
|
;
|
||||||
|
Wordlist
|
||||||
|
: Wordlist Word << ast.AppendToWordlist($0, $1) >>
|
||||||
|
| Word << ast.NewWordlist($0) >>
|
||||||
|
;
|
||||||
|
CaseClause
|
||||||
|
: "case" Word Linebreak "in" Linebreak CaseList "esac" << ast.NewCaseClause($1, $5) >>
|
||||||
|
| "case" Word Linebreak "in" Linebreak CaseListNs "esac" << ast.NewCaseClause($1, $5) >>
|
||||||
|
| "case" Word Linebreak "in" Linebreak "esac" << ast.NewCaseClause($1, ast.CaseList{}) >>
|
||||||
|
;
|
||||||
|
CaseListNs
|
||||||
|
: CaseList CaseItemNs << ast.AppendToCaseList($0, $1) >>
|
||||||
|
| CaseItemNs << ast.NewCaseList($0) >>
|
||||||
|
;
|
||||||
|
CaseList
|
||||||
|
: CaseList CaseItem << ast.AppendToCaseList($0, $1) >>
|
||||||
|
| CaseItem << ast.NewCaseList($0) >>
|
||||||
|
;
|
||||||
|
CaseItemNs
|
||||||
|
: Pattern ")" Linebreak << ast.NewCaseItem($0, nil) >>
|
||||||
|
| Pattern ")" CompoundList Linebreak << ast.NewCaseItem($0, $2) >>
|
||||||
|
| "(" Pattern ")" Linebreak << ast.NewCaseItem($1, nil) >>
|
||||||
|
| "(" Pattern ")" CompoundList Linebreak << ast.NewCaseItem($1, $3) >>
|
||||||
|
;
|
||||||
|
CaseItem
|
||||||
|
: Pattern ")" Linebreak ";;" Linebreak << ast.NewCaseItem($0, nil) >>
|
||||||
|
| Pattern ")" CompoundList ";;" Linebreak << ast.NewCaseItem($0, $2) >>
|
||||||
|
| "(" Pattern ")" Linebreak ";;" Linebreak << ast.NewCaseItem($1, nil) >>
|
||||||
|
| "(" Pattern ")" CompoundList ";;" Linebreak << ast.NewCaseItem($1, $3) >>
|
||||||
|
;
|
||||||
|
Pattern
|
||||||
|
: Word << ast.NewPattern($0) >> /* Apply rule 4 */
|
||||||
|
| Pattern "|" Word << ast.AppendToPattern($0, $2) >> /* Do not apply rule 4 */
|
||||||
|
;
|
||||||
|
IfClause
|
||||||
|
: "if" CompoundList "then" CompoundList ElsePart "fi" << ast.NewIfClause($1, $3, $4) >>
|
||||||
|
| "if" CompoundList "then" CompoundList "fi" << ast.NewIfClause($1, $3, nil) >>
|
||||||
|
;
|
||||||
|
ElsePart
|
||||||
|
: "elif" CompoundList "then" ElsePart << ast.NewIfClause($1, nil, $3) >>
|
||||||
|
| "else" CompoundList << ast.NewIfClause(nil, $1, nil) >>
|
||||||
|
;
|
||||||
|
WhileClause
|
||||||
|
: "while" CompoundList DoGroup << ast.NewWhileClause($1, $2) >>
|
||||||
|
;
|
||||||
|
UntilClause
|
||||||
|
: "until" CompoundList DoGroup << ast.NewUntilClause($1, $2) >>
|
||||||
|
;
|
||||||
|
FunctionDefinition
|
||||||
|
: FunctionName "(" ")" Linebreak FunctionBody << ast.NewFunctionDefinition($0, $4) >>
|
||||||
|
;
|
||||||
|
FunctionBody
|
||||||
|
: CompoundCommand << ast.NewFunctionBody($0, ast.RedirectList{}) >> /* Apply rule 9 */
|
||||||
|
| CompoundCommand RedirectList << ast.NewFunctionBody($0, $1) >> /* Apply rule 9 */
|
||||||
|
;
|
||||||
|
FunctionName
|
||||||
|
: Word << ast.NewFunctionName($0) >> /* Apply rule 8 */
|
||||||
|
;
|
||||||
|
BraceGroup
|
||||||
|
: "{" CompoundList "}" << ast.NewBraceGroup($1) >>
|
||||||
|
;
|
||||||
|
DoGroup
|
||||||
|
: "do" CompoundList "done" << ast.NewDoGroup($1) >> /* Apply rule 6 */
|
||||||
|
;
|
||||||
|
SimpleCommand
|
||||||
|
: CmdPrefix CmdWord CmdSuffix << ast.NewSimpleCommand($0, nil, $1, $2) >>
|
||||||
|
| CmdPrefix CmdWord << ast.NewSimpleCommand($0, nil, $1, nil) >>
|
||||||
|
| CmdPrefix << ast.NewSimpleCommand($0, nil, nil, nil) >>
|
||||||
|
| CmdName CmdSuffix << ast.NewSimpleCommand(nil, $0, nil, $1) >>
|
||||||
|
| CmdName << ast.NewSimpleCommand(nil, $0, nil, nil) >>
|
||||||
|
;
|
||||||
|
CmdName
|
||||||
|
: Word << ast.NewCmdName($0) >> /* Apply rule 7a */
|
||||||
|
;
|
||||||
|
CmdWord
|
||||||
|
: Word << ast.NewCmdWord($0) >> /* Apply rule 7b */
|
||||||
|
;
|
||||||
|
CmdPrefix
|
||||||
|
: IoRedirect << ast.NewCmdPrefix(nil, $0, nil) >>
|
||||||
|
| CmdPrefix IoRedirect << ast.NewCmdPrefix(nil, $1, $0) >>
|
||||||
|
| AssignmentWord << ast.NewCmdPrefix($0, nil, nil) >>
|
||||||
|
| CmdPrefix AssignmentWord << ast.NewCmdPrefix($1, nil, $0) >>
|
||||||
|
;
|
||||||
|
CmdSuffix
|
||||||
|
: IoRedirect << ast.NewCmdSuffix(ast.Word(""), $0, nil) >>
|
||||||
|
| CmdSuffix IoRedirect << ast.NewCmdSuffix(ast.Word(""), $1, $0) >>
|
||||||
|
| Word << ast.NewCmdSuffix($0, nil, nil) >>
|
||||||
|
| CmdSuffix Word << ast.NewCmdSuffix($1, nil, $0) >>
|
||||||
|
;
|
||||||
|
RedirectList
|
||||||
|
: IoRedirect << ast.NewRedirectList($0) >>
|
||||||
|
| RedirectList IoRedirect << ast.AppendToRedirectList($0, $1) >>
|
||||||
|
;
|
||||||
|
IoRedirect
|
||||||
|
: IoFile << ast.NewIoRedirect($0, ast.IoNumber(0), nil) >>
|
||||||
|
| IoNumber IoFile << ast.NewIoRedirect($1, $0, nil) >>
|
||||||
|
| IoHere << ast.NewIoRedirect(nil, ast.IoNumber(0), $0) >>
|
||||||
|
| IoNumber IoHere << ast.NewIoRedirect(nil, $0, $1) >>
|
||||||
|
;
|
||||||
|
IoFile
|
||||||
|
: "<" Filename << ast.NewIoFile($1, ast.R_STDIN) >>
|
||||||
|
| "<&" Filename << ast.NewIoFile($1, ast.R_INFD) >>
|
||||||
|
| ">" Filename << ast.NewIoFile($1, ast.R_STDOUT) >>
|
||||||
|
| ">&" Filename << ast.NewIoFile($1, ast.R_OUTFD) >>
|
||||||
|
| ">>" Filename << ast.NewIoFile($1, ast.R_APPEND) >>
|
||||||
|
| "<>" Filename << ast.NewIoFile($1, ast.R_ORWFD) >>
|
||||||
|
| ">|" Filename << ast.NewIoFile($1, ast.R_OUTSP) >>
|
||||||
|
;
|
||||||
|
Filename
|
||||||
|
: Word << ast.NewFilename($0) >> /* Apply rule 2 */
|
||||||
|
;
|
||||||
|
IoHere
|
||||||
|
: "<<" HereEnd << ast.NewIoHere($1, false) >>
|
||||||
|
| "<<-" HereEnd << ast.NewIoHere($1, true) >>
|
||||||
|
;
|
||||||
|
HereEnd
|
||||||
|
: Word << ast.NewHereEnd($0) >> /* Apply rule 3 */
|
||||||
|
;
|
||||||
|
NewlineList
|
||||||
|
: "\n"
|
||||||
|
| NewlineList "\n"
|
||||||
|
;
|
||||||
|
Linebreak
|
||||||
|
: NewlineList
|
||||||
|
| nothing
|
||||||
|
;
|
||||||
|
SeparatorOp
|
||||||
|
: "&" << ast.S_AMP, nil >>
|
||||||
|
| ";" << ast.S_SEMICOLON, nil >>
|
||||||
|
;
|
||||||
|
Separator
|
||||||
|
: SeparatorOp Linebreak << ast.NewSeparator($0) >>
|
||||||
|
| NewlineList
|
||||||
|
;
|
||||||
|
SequentialSep
|
||||||
|
: ";" Linebreak
|
||||||
|
| NewlineList
|
||||||
|
;
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
foo) echo "FOOOOO!" ;;
|
||||||
|
bar) echo "BAAAAAR!" ;;
|
||||||
|
baz) echo "BAAAZZZ!" ;;
|
||||||
|
*) echo "WHATEVER!"
|
||||||
|
esac
|
|
@ -0,0 +1,8 @@
|
||||||
|
for i in 0..3
|
||||||
|
do
|
||||||
|
echo "i = $i"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ true ]]; then
|
||||||
|
echo "Truth!"
|
||||||
|
fi
|
|
@ -0,0 +1,26 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kr/pretty"
|
||||||
|
"github.com/localhots/penny/lexer"
|
||||||
|
"github.com/localhots/penny/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWorld(t *testing.T) {
|
||||||
|
b, err := ioutil.ReadFile("example.sh")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lex := lexer.NewLexer(b)
|
||||||
|
p := parser.NewParser()
|
||||||
|
st, err := p.Parse(lex)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pretty.Println(st)
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
|
||||||
|
package token
|
||||||
|
|
||||||
|
import(
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
Type
|
||||||
|
Lit []byte
|
||||||
|
Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
type Type int
|
||||||
|
|
||||||
|
const(
|
||||||
|
INVALID Type = iota
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pos struct {
|
||||||
|
Offset int
|
||||||
|
Line int
|
||||||
|
Column int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this Pos) String() string {
|
||||||
|
return fmt.Sprintf("Pos(offset=%d, line=%d, column=%d)", this.Offset, this.Line, this.Column)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenMap struct {
|
||||||
|
typeMap []string
|
||||||
|
idMap map[string]Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this TokenMap) Id(tok Type) string {
|
||||||
|
if int(tok) < len(this.typeMap) {
|
||||||
|
return this.typeMap[tok]
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this TokenMap) Type(tok string) Type {
|
||||||
|
if typ, exist := this.idMap[tok]; exist {
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
return INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this TokenMap) TokenString(tok *Token) string {
|
||||||
|
//TODO: refactor to print pos & token string properly
|
||||||
|
return fmt.Sprintf("%s(%d,%s)", this.Id(tok.Type), tok.Type, tok.Lit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this TokenMap) StringType(typ Type) string {
|
||||||
|
return fmt.Sprintf("%s(%d)", this.Id(typ), typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
var TokMap = TokenMap{
|
||||||
|
typeMap: []string{
|
||||||
|
"INVALID",
|
||||||
|
"$",
|
||||||
|
"word",
|
||||||
|
"number",
|
||||||
|
"=",
|
||||||
|
"&&",
|
||||||
|
"||",
|
||||||
|
"!",
|
||||||
|
"|",
|
||||||
|
"(",
|
||||||
|
")",
|
||||||
|
"for",
|
||||||
|
"in",
|
||||||
|
"name",
|
||||||
|
"case",
|
||||||
|
"esac",
|
||||||
|
";;",
|
||||||
|
"if",
|
||||||
|
"then",
|
||||||
|
"fi",
|
||||||
|
"elif",
|
||||||
|
"else",
|
||||||
|
"while",
|
||||||
|
"until",
|
||||||
|
"{",
|
||||||
|
"}",
|
||||||
|
"do",
|
||||||
|
"done",
|
||||||
|
"<",
|
||||||
|
"<&",
|
||||||
|
">",
|
||||||
|
">&",
|
||||||
|
">>",
|
||||||
|
"<>",
|
||||||
|
">|",
|
||||||
|
"<<",
|
||||||
|
"<<-",
|
||||||
|
"\n",
|
||||||
|
"nothing",
|
||||||
|
"&",
|
||||||
|
";",
|
||||||
|
},
|
||||||
|
|
||||||
|
idMap: map[string]Type {
|
||||||
|
"INVALID": 0,
|
||||||
|
"$": 1,
|
||||||
|
"word": 2,
|
||||||
|
"number": 3,
|
||||||
|
"=": 4,
|
||||||
|
"&&": 5,
|
||||||
|
"||": 6,
|
||||||
|
"!": 7,
|
||||||
|
"|": 8,
|
||||||
|
"(": 9,
|
||||||
|
")": 10,
|
||||||
|
"for": 11,
|
||||||
|
"in": 12,
|
||||||
|
"name": 13,
|
||||||
|
"case": 14,
|
||||||
|
"esac": 15,
|
||||||
|
";;": 16,
|
||||||
|
"if": 17,
|
||||||
|
"then": 18,
|
||||||
|
"fi": 19,
|
||||||
|
"elif": 20,
|
||||||
|
"else": 21,
|
||||||
|
"while": 22,
|
||||||
|
"until": 23,
|
||||||
|
"{": 24,
|
||||||
|
"}": 25,
|
||||||
|
"do": 26,
|
||||||
|
"done": 27,
|
||||||
|
"<": 28,
|
||||||
|
"<&": 29,
|
||||||
|
">": 30,
|
||||||
|
">&": 31,
|
||||||
|
">>": 32,
|
||||||
|
"<>": 33,
|
||||||
|
">|": 34,
|
||||||
|
"<<": 35,
|
||||||
|
"<<-": 36,
|
||||||
|
"\n": 37,
|
||||||
|
"nothing": 38,
|
||||||
|
"&": 39,
|
||||||
|
";": 40,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
|
||||||
|
//Copyright 2013 Vastech SA (PTY) LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Interface */
|
||||||
|
|
||||||
|
/*
|
||||||
|
Convert the literal value of a scanned token to rune
|
||||||
|
*/
|
||||||
|
func RuneValue(lit []byte) rune {
|
||||||
|
if lit[1] == '\\' {
|
||||||
|
return escapeCharVal(lit)
|
||||||
|
}
|
||||||
|
r, size := utf8.DecodeRune(lit[1:])
|
||||||
|
if size != len(lit)-2 {
|
||||||
|
panic(fmt.Sprintf("Error decoding rune. Lit: %s, rune: %d, size%d\n", lit, r, size))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Convert the literal value of a scanned token to int64
|
||||||
|
*/
|
||||||
|
func IntValue(lit []byte) (int64, error) {
|
||||||
|
return strconv.ParseInt(string(lit), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Convert the literal value of a scanned token to uint64
|
||||||
|
*/
|
||||||
|
func UintValue(lit []byte) (uint64, error) {
|
||||||
|
return strconv.ParseUint(string(lit), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Util */
|
||||||
|
|
||||||
|
func escapeCharVal(lit []byte) rune {
|
||||||
|
var i, base, max uint32
|
||||||
|
offset := 2
|
||||||
|
switch lit[offset] {
|
||||||
|
case 'a':
|
||||||
|
return '\a'
|
||||||
|
case 'b':
|
||||||
|
return '\b'
|
||||||
|
case 'f':
|
||||||
|
return '\f'
|
||||||
|
case 'n':
|
||||||
|
return '\n'
|
||||||
|
case 'r':
|
||||||
|
return '\r'
|
||||||
|
case 't':
|
||||||
|
return '\t'
|
||||||
|
case 'v':
|
||||||
|
return '\v'
|
||||||
|
case '\\':
|
||||||
|
return '\\'
|
||||||
|
case '\'':
|
||||||
|
return '\''
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||||
|
i, base, max = 3, 8, 255
|
||||||
|
case 'x':
|
||||||
|
i, base, max = 2, 16, 255
|
||||||
|
offset++
|
||||||
|
case 'u':
|
||||||
|
i, base, max = 4, 16, unicode.MaxRune
|
||||||
|
offset++
|
||||||
|
case 'U':
|
||||||
|
i, base, max = 8, 16, unicode.MaxRune
|
||||||
|
offset++
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Error decoding character literal: %s\n", lit))
|
||||||
|
}
|
||||||
|
|
||||||
|
var x uint32
|
||||||
|
for ; i > 0 && offset < len(lit)-1; i-- {
|
||||||
|
ch, size := utf8.DecodeRune(lit[offset:])
|
||||||
|
offset += size
|
||||||
|
d := uint32(digitVal(ch))
|
||||||
|
if d >= base {
|
||||||
|
panic(fmt.Sprintf("charVal(%s): illegal character (%c) in escape sequence. size=%d, offset=%d", lit, ch, size, offset))
|
||||||
|
}
|
||||||
|
x = x*base + d
|
||||||
|
}
|
||||||
|
if x > max || 0xD800 <= x && x < 0xE000 {
|
||||||
|
panic(fmt.Sprintf("Error decoding escape char value. Lit:%s, offset:%d, escape sequence is invalid Unicode code point\n", lit, offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rune(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func digitVal(ch rune) int {
|
||||||
|
switch {
|
||||||
|
case '0' <= ch && ch <= '9':
|
||||||
|
return int(ch) - '0'
|
||||||
|
case 'a' <= ch && ch <= 'f':
|
||||||
|
return int(ch) - 'a' + 10
|
||||||
|
case 'A' <= ch && ch <= 'F':
|
||||||
|
return int(ch) - 'A' + 10
|
||||||
|
}
|
||||||
|
return 16 // larger than any legal digit val
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
|
||||||
|
//Copyright 2013 Vastech SA (PTY) LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RuneToString(r rune) string {
|
||||||
|
if r >= 0x20 && r < 0x7f {
|
||||||
|
return fmt.Sprintf("'%c'", r)
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case 0x07:
|
||||||
|
return "'\\a'"
|
||||||
|
case 0x08:
|
||||||
|
return "'\\b'"
|
||||||
|
case 0x0C:
|
||||||
|
return "'\\f'"
|
||||||
|
case 0x0A:
|
||||||
|
return "'\\n'"
|
||||||
|
case 0x0D:
|
||||||
|
return "'\\r'"
|
||||||
|
case 0x09:
|
||||||
|
return "'\\t'"
|
||||||
|
case 0x0b:
|
||||||
|
return "'\\v'"
|
||||||
|
case 0x5c:
|
||||||
|
return "'\\\\\\'"
|
||||||
|
case 0x27:
|
||||||
|
return "'\\''"
|
||||||
|
case 0x22:
|
||||||
|
return "'\\\"'"
|
||||||
|
}
|
||||||
|
if r < 0x10000 {
|
||||||
|
return fmt.Sprintf("\\u%04x", r)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("\\U%08x", r)
|
||||||
|
}
|
Loading…
Reference in New Issue