This adds a for statement which is used to iterate over a list with a body of statements. This is an important data transformation tool which should be used sparingly, but is important to have. An import statement inside of a for loop is not currently supported. We have a simple hack to detect the obvious cases, but more deeply nested scenarios probably won't be caught, and you'll get an obscure error message if you try to do this. This was incredibly challenging to get right, and it's all thanks to Sam for his brilliance. Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
406 lines
9.3 KiB
Plaintext
406 lines
9.3 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()
|
|
// sanity check... these should be the same!
|
|
if x, y := lval.str, interfaces.ClassSep; x != y {
|
|
panic(fmt.Sprintf("COLON does not match ClassSep (%s != %s)", x, y))
|
|
}
|
|
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
|
|
}
|
|
/\*/ {
|
|
// This is used as the multiplication symbol, but also
|
|
// (for now) the bare import feature, eg: `import as *`.
|
|
yylex.pos(lval) // our pos
|
|
lval.str = yylex.Text()
|
|
// sanity check... these should be the same!
|
|
if x, y := lval.str, interfaces.BareSymbol; x != y {
|
|
panic(fmt.Sprintf("MULTIPLY does not match BareSymbol (%s != %s)", x, y))
|
|
}
|
|
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
|
|
}
|
|
/in/ {
|
|
yylex.pos(lval) // our pos
|
|
lval.str = yylex.Text()
|
|
return IN
|
|
}
|
|
/for/ {
|
|
yylex.pos(lval) // our pos
|
|
lval.str = yylex.Text()
|
|
return FOR
|
|
}
|
|
/\->/ {
|
|
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
|
|
}
|
|
/[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
|
|
}
|
|
/#[^\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:] // 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) 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 <https://www.gnu.org/licenses/>.
|
|
//
|
|
// Additional permission under GNU GPL version 3 section 7
|
|
//
|
|
// If you modify this program, or any covered work, by linking or combining it
|
|
// with embedded mcl code and modules (and that the embedded mcl code and
|
|
// modules which link with this program, contain a copy of their source code in
|
|
// the authoritative form) containing parts covered by the terms of any other
|
|
// license, the licensors of this program grant you additional permission to
|
|
// convey the resulting work. Furthermore, the licensors of this program grant
|
|
// the original author, James Shubin, additional permission to update this
|
|
// additional permission if he deems it necessary to achieve the goals of this
|
|
// additional permission.
|
|
|
|
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.
|