blob: 7701f4c239ca6dc22965f86b045fc19fbc75b4ca [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 parse
import (
"fmt"
"strings"
"mynewt.apache.org/newt/util"
)
type TokenCode int
const (
TOKEN_NOT_EQUALS TokenCode = iota
TOKEN_NOT
TOKEN_EQUALS
TOKEN_LT
TOKEN_LTE
TOKEN_GT
TOKEN_GTE
TOKEN_AND
TOKEN_OR
TOKEN_XOR
TOKEN_LPAREN
TOKEN_RPAREN
TOKEN_STRING
TOKEN_NUMBER
TOKEN_IDENT
)
type Token struct {
Code TokenCode
Text string
Offset int
}
// Returns length of token on success; 0 if no match.
type LexFn func(s string) (string, int, error)
const delimChars = "!='\"&|^() \t\n"
func lexString(s string, sought string) (string, int, error) {
if strings.HasPrefix(s, sought) {
return sought, len(sought), nil
}
return "", 0, nil
}
func lexStringFn(sought string) LexFn {
return func(s string) (string, int, error) { return lexString(s, sought) }
}
func lexLitNumber(s string) (string, int, error) {
var sub string
idx := strings.IndexAny(s, delimChars)
if idx == -1 {
sub = s
} else {
sub = s[:idx]
}
if _, ok := util.AtoiNoOctTry(sub); ok {
return sub, len(sub), nil
}
return "", 0, nil
}
func lexLitString(s string) (string, int, error) {
if s[0] != '"' {
return "", 0, nil
}
quote2 := strings.IndexByte(s[1:], '"')
if quote2 == -1 {
return "", 0, fmt.Errorf("unterminated quote: %s", s)
}
return s[1 : quote2+1], quote2 + 2, nil
}
func lexIdent(s string) (string, int, error) {
idx := strings.IndexAny(s, delimChars)
if idx == -1 {
return s, len(s), nil
} else {
return s[:idx], idx, nil
}
}
type lexEntry struct {
code TokenCode
fn LexFn
}
var lexEntries = []lexEntry{
{TOKEN_NOT_EQUALS, lexStringFn("!=")},
{TOKEN_EQUALS, lexStringFn("==")},
{TOKEN_AND, lexStringFn("&&")},
{TOKEN_OR, lexStringFn("||")},
{TOKEN_XOR, lexStringFn("^^")},
{TOKEN_LTE, lexStringFn("<=")},
{TOKEN_GTE, lexStringFn(">=")},
{TOKEN_NOT, lexStringFn("!")},
{TOKEN_LT, lexStringFn("<")},
{TOKEN_GT, lexStringFn(">")},
{TOKEN_LPAREN, lexStringFn("(")},
{TOKEN_RPAREN, lexStringFn(")")},
{TOKEN_STRING, lexLitString},
{TOKEN_NUMBER, lexLitNumber},
{TOKEN_IDENT, lexIdent},
}
func lexOneToken(expr string, offset int) (Token, int, error) {
var t Token
subexpr := expr[offset:]
for _, e := range lexEntries {
text, sz, err := e.fn(subexpr)
if err != nil {
return t, 0, err
}
if sz != 0 {
t.Code = e.code
t.Text = text
t.Offset = offset
return t, sz, nil
}
}
return t, 0, nil
}
func skipSpaceLeft(s string, offset int) int {
sub := s[offset:]
newSub := strings.TrimLeft(sub, " \t\n'")
return len(sub) - len(newSub)
}
// Tokenizes a string.
func Lex(expr string) ([]Token, error) {
tokens := []Token{}
off := 0
off += skipSpaceLeft(expr, off)
for off < len(expr) {
t, skip, err := lexOneToken(expr, off)
if err != nil {
return nil, err
}
if skip == 0 {
return nil, fmt.Errorf("Invalid token starting with: %s", expr)
}
tokens = append(tokens, t)
off += skip
off += skipSpaceLeft(expr, off)
}
return tokens, nil
}
// Produces a string representation of a token sequence.
func SprintfTokens(tokens []Token) string {
s := ""
for _, t := range tokens {
switch t.Code {
case TOKEN_NUMBER:
s += fmt.Sprintf("#[%s] ", t.Text)
case TOKEN_IDENT:
s += fmt.Sprintf("i[%s] ", t.Text)
case TOKEN_STRING:
s += fmt.Sprintf("\"%s\" ", t.Text)
default:
s += fmt.Sprintf("%s ", t.Text)
}
}
return s
}