Files
mgmt/lang/parser/lexer.nex
James Shubin 2cbce963b7 engine: resources, lang: funcs, parser: Add panic magic
It's valuable to check your runtime values and to shut down the entire
engine in case something doesn't match. This patch adds some magic
plumbing to support a "panic" mechanism.

A new "panic" statement gets transparently converted into a panic
function and panic resource. The former errors if the input is not
empty. The latter must be present to consume the value, but doesn't
actually do anything.
2023-11-28 13:49:31 -05:00

385 lines
8.2 KiB
Plaintext

/[ \t\n]/ { /* skip over whitespace */ }
/{/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return OPEN_CURLY
}
/}/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return CLOSE_CURLY
}
/\(/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return OPEN_PAREN
}
/\)/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return CLOSE_PAREN
}
/\[/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return OPEN_BRACK
}
/\]/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return CLOSE_BRACK
}
/if/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return IF
}
/else/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return ELSE
}
/\?:/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return ELVIS
}
/\|\|/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return DEFAULT
}
/=>/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return ROCKET
}
/,/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return COMMA
}
/:/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return COLON
}
/;/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return SEMICOLON
}
/=/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return EQUALS
}
/\+/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return PLUS
}
/\-/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return MINUS
}
/\*/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return MULTIPLY
}
/\// {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return DIVIDE
}
/==/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return EQ
}
/!=/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return NEQ
}
/</ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return LT
}
/>/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return GT
}
/<=/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return LTE
}
/>=/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return GTE
}
/and/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return AND
}
/or/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return OR
}
/not/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return NOT
}
/\->/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return ARROW
}
/\./ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
// sanity check... these should be the same!
if x, y := lval.str, interfaces.ModuleSep; x != y {
panic(fmt.Sprintf("DOT does not match ModuleSep (%s != %s)", x, y))
}
return DOT
}
/\$/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return DOLLAR
}
/bool/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return BOOL_IDENTIFIER
}
/str/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return STR_IDENTIFIER
}
/int/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return INT_IDENTIFIER
}
/float/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return FLOAT_IDENTIFIER
}
/map/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return MAP_IDENTIFIER
}
/struct/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return STRUCT_IDENTIFIER
}
/func/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return FUNC_IDENTIFIER
}
/class/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return CLASS_IDENTIFIER
}
/include/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return INCLUDE_IDENTIFIER
}
/import/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return IMPORT_IDENTIFIER
}
/as/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return AS_IDENTIFIER
}
/variant/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return VARIANT_IDENTIFIER
}
/true|false/ {
yylex.pos(lval) // our pos
s := yylex.Text()
if s == "true" {
lval.bool = true
} else if s == "false" {
lval.bool = false
} else {
// the lexer was wrong
panic(fmt.Sprintf("error lexing BOOL, got: %s", s))
}
return BOOL
}
/panic/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return PANIC_IDENTIFIER
}
/"(\\.|[^"])*"/
{ // This matches any number of the bracketed patterns
// that are surrounded by the two quotes on each side.
// The bracket pattern is any escaped char or something
// that is not a single quote char. See this reference:
// https://www.lysator.liu.se/c/ANSI-C-grammar-l.html#STRING-LITERAL
// old: /"[\a\b\t\n\v\f\r !#$%&'()*+,-.\/0-9:;<=>?@A-Z\[\\\]^_a-z{|}~]*"/
yylex.pos(lval) // our pos
s := yylex.Text()
if s[0:1] != "\"" || s[len(s)-1:] != "\"" {
// unhandled error
panic(fmt.Sprintf("error lexing STRING, got: %s", s))
//return ERROR // unreachable
}
lval.str = s[1:len(s)-1] // remove the two quotes
return STRING
}
/\-?[0-9]+/
{
yylex.pos(lval) // our pos
s := yylex.Text()
var err error
lval.int, err = strconv.ParseInt(s, 10, 64) // int64
if err == nil {
return INTEGER
} else if e := err.(*strconv.NumError); e.Err == strconv.ErrRange {
// this catches range errors for very large ints
lp := yylex.cast()
lp.lexerErr = &LexParseErr{
Err: ErrLexerIntegerOverflow,
Str: s,
Row: yylex.Line(),
Col: yylex.Column(),
}
return ERROR
} else {
panic(fmt.Sprintf("error lexing INTEGER, got: %v", err))
}
}
/\-?[0-9]+\.[0-9]+/
{
yylex.pos(lval) // our pos
s := yylex.Text()
var err error
lval.float, err = strconv.ParseFloat(s, 64) // float64
if err == nil {
return FLOAT
} else if e := err.(*strconv.NumError); e.Err == strconv.ErrRange {
// this catches range errors for very large floats
lp := yylex.cast()
lp.lexerErr = &LexParseErr{
Err: ErrLexerFloatOverflow,
Str: s,
Row: yylex.Line(),
Col: yylex.Column(),
}
return ERROR
} else {
panic(fmt.Sprintf("error lexing FLOAT, got: %v", err))
}
}
/[a-z]([a-z0-9_]*[a-z0-9]+)?/
{
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return IDENTIFIER
}
/in/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return IN
}
/[A-Z]([a-z0-9_]*[a-z0-9]+)?/
{
yylex.pos(lval) // our pos
s := yylex.Text()
lval.str = strings.ToLower(s) // uncapitalize it
return CAPITALIZED_IDENTIFIER
}
/[a-z]([a-z0-9:]*[a-z0-9]+)?/
{
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return RES_IDENTIFIER
}
/#[^\n]*/
{ // this matches a (#) pound char followed by any
// number of chars that aren't the (\n) newline!
yylex.pos(lval) // our pos
s := yylex.Text()
lval.str = s[1:len(s)] // remove the leading #
//log.Printf("lang: lexer: comment: `%s`", lval.str)
//return COMMENT // skip return to avoid parsing
}
/./ {
yylex.pos(lval) // our pos
s := yylex.Text()
lp := yylex.cast()
e := ErrLexerUnrecognized
if s == "\r" { // windows!
e = ErrLexerUnrecognizedCR
}
lp.lexerErr = &LexParseErr{
Err: e,
Str: s,
Row: yylex.Line(),
Col: yylex.Column(),
}
return ERROR
}
//
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package parser
import (
"fmt"
"strconv"
"github.com/purpleidea/mgmt/lang/interfaces"
)
// NOTE:
// Among rules in the same scope, the longest matching pattern takes precedence.
// In event of a tie, the first pattern wins.