package main import ( "strings" "strconv" "fmt" "main/walk" "main/subex" ) type parser struct { tokenStream chan Token rewinds []Token } func (p *parser) next() Token { var token Token if len(p.rewinds) == 0 { token = <- p.tokenStream } else { token = p.rewinds[len(p.rewinds)-1] p.rewinds = p.rewinds[:len(p.rewinds)-1] } if token.typ == TokenErr { fmt.Println(token) panic("Lexing error") } return token } func (p *parser) rewind(token Token) { p.rewinds = append(p.rewinds, token) } func (p *parser) peek() Token { token := p.next() p.rewind(token) return token } var segmentTokens map[TokenType]bool = map[TokenType]bool { TokenHash: true, TokenAt: true, TokenDot: true, TokenLBrack: true, } func (p *parser) parsePathPatternFilter(minPower int) PathFilterAST { var lhs PathFilterAST token := p.next() switch token.typ { case TokenHash: stringIndex := p.next() if stringIndex.typ != TokenPatternStringIndex { panic("Expected string index after # in pattern") } lhs = StringSegmentPathFilterAST{stringIndex.val} case TokenAt: intIndex := p.next() if intIndex.typ != TokenPatternIntegerIndex { panic("Expected integer index after @ in pattern") } index, err := strconv.Atoi(intIndex.val) if err != nil { panic("Expected integer index after @ in pattern") } lhs = IntegerSegmentPathFilterAST{index} case TokenDot: lhs = AnySegmentPathFilterAST{} case TokenLBrack: lhs = p.parsePathPatternFilter(0) if p.next().typ != TokenRBrack { panic("Expected ] in path filter") } default: panic("Expected path pattern filter segment") } loop: for { token = p.next() switch { case token.typ == TokenAst && 10 >= minPower: lhs = RepeatPathFilterAST {lhs} case token.typ == TokenQuestion && 10 >= minPower: lhs = OrPathFilterAST{lhs, NonePathFilterAST{}} case token.typ == TokenBar && 0 >= minPower: lhs = OrPathFilterAST{lhs, p.parsePathPatternFilter(1)} case segmentTokens[token.typ] && 2 >= minPower: p.rewind(token) lhs = SequencePathFilterAST {lhs, p.parsePathPatternFilter(3)} default: p.rewind(token) break loop } } return lhs } func (p *parser) parseFilter(minPower int) Filter { var lhs Filter token := p.next() switch token.typ { case TokenHash, TokenAt, TokenDot, TokenLBrack: p.rewind(token) filterAst := p.parsePathPatternFilter(0) lhs = compilePathFilterAST(filterAst) case TokenHat: lhs = BeginTerminalFilter{} case TokenDollar: lhs = EndTerminalFilter{} case TokenHatDollar: lhs = TerminalFilter{} case TokenTilde: lhs = RootFilter{} case TokenLParen: lhs = p.parseFilter(0) rParen := p.next() if rParen.typ != TokenRParen { panic("Missing ) in filter") } default: panic("Expected filter") } loop: for { token = p.next() switch { case token.typ == TokenAnd && 2 >= minPower: lhs = AndFilter {lhs, p.parseFilter(3)} case token.typ == TokenOr && 0 >= minPower: lhs = OrFilter {lhs, p.parseFilter(1)} default: p.rewind(token) break loop } } return lhs } func (p *parser) parseLiterals() (items []walk.WalkItem) { var path walk.Path var value walk.WalkValue loop: for { token := p.next() switch token.typ { case TokenSemicolon, TokenEOF: p.rewind(token) break loop case TokenComma: case TokenNullLiteral: value = walk.ValueNull{} case TokenTrueLiteral: value = walk.ValueBool(true) case TokenFalseLiteral: value = walk.ValueBool(false) case TokenNumberLiteral: numberLiteral, err := strconv.ParseFloat(token.val, 64) if err != nil { panic("Error parsing number literal to float64") } value = walk.ValueNumber(numberLiteral) case TokenDoubleQuote: stringToken := p.next() if stringToken.typ != TokenStringLiteral { panic("Expected string literal after \"") } // TODO: resolve escape characters stringLiteral := stringToken.val if p.next().typ != TokenDoubleQuote { panic("Expected \" after string literal") } colon := p.next() if colon.typ == TokenColon { if path != nil { panic("Expected value after path:") } path = walk.Path{stringLiteral} } else { p.rewind(colon) value = walk.ValueString(stringLiteral) } case TokenTerminalLiteral: switch token.val { case "{": value = walk.MapBegin case "}": value = walk.MapEnd case "[": value = walk.ArrayBegin case "]": value = walk.ArrayEnd default: panic("Invalid terminal token") } } if value != nil { items = append(items, walk.WalkItem { Path: path, Value: value, }) path = nil value = nil } } if path != nil { panic("Expected value after path:") } return items } func (p *parser) parseSubex() subex.SubexState { delim := p.next() if delim.typ != TokenSubstituteDelimiter { panic("Missing substitute delimiter") } subexProgramToken := p.next() if subexProgramToken.typ != TokenSubex { panic("Missing subex from substitution") } reader := subex.NewStringRuneReader(subexProgramToken.val) subexAST := subex.Parse(reader) subex := subex.CompileTransducer(subexAST) delim = p.next() if delim.typ != TokenSubstituteDelimiter { panic("Missing end substitute delimiter") } return subex } func (p *parser) parseBasicCommand(commandChar rune) Command { switch commandChar { case 'p': return PrintValueCommand{} case 'd': return DeleteAllCommand{} case 'n': return NextCommand{} case 'N': return AppendNextCommand{} case 's': subex := p.parseSubex() var next Command token := p.peek() switch token.typ { case TokenEOF, TokenRBrace: next = NoopCommand{} default: next = p.parseCommand() } return SubstituteCommand { subex: subex, next: next, } case 'i': items := p.parseLiterals() return PrintLiteralsCommand {items: items} case 'o': return NoopCommand{} case 'x': return SwapXRegCommand{} case 'X': return AppendXRegCommand{} case 'k': return SwapPathCommand{} case 'K': return AppendPathCommand{} default: panic("Invalid command") } } func (p *parser) parseCommand() Command { token := p.next() switch token.typ { case TokenHash, TokenAt, TokenDot, TokenLParen, TokenHat, TokenDollar, TokenHatDollar, TokenTilde: p.rewind(token) filter := p.parseFilter(0) notToken := p.next() if notToken.typ == TokenExclamation { filter = NotFilter {filter} } else { p.rewind(notToken) } command := p.parseCommand() command = FilteredCommand { filter: filter, command: command, } return command case TokenLBrace: commands := p.parseCommands() if p.next().typ != TokenRBrace { panic("Missing matching }") } return SequenceCommand {commands} case TokenCommand: commandChar, _, err := strings.NewReader(token.val).ReadRune() if err != nil { panic("Error reading a command character!?") } return p.parseBasicCommand(commandChar) default: panic("Invalid token, expected command") } } func (p *parser) parseCommands() []Command { var commands []Command for { nextToken := p.peek() if nextToken.typ == TokenEOF || nextToken.typ == TokenRBrace { return commands } commands = append(commands, p.parseCommand()) endToken := p.peek() if endToken.typ == TokenEOF || endToken.typ == TokenRBrace { return commands } } } func Parse(tokens chan Token) []Command { p := parser { tokenStream: tokens, } return p.parseCommands() }