/[ \t]/		{ /* skip over tabs and spaces */ }
/[\n]/		{
			yylex.pos(lval) // our pos
			return NEWLINE
		}
/{/		{
			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
		}
/forkv/		{
			yylex.pos(lval) // our pos
			lval.str = yylex.Text()
			return FORKV
		}
/\->/		{
			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
		}
/collect/	{
			yylex.pos(lval) // our pos
			lval.str = yylex.Text()
			return COLLECT_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)
			// We don't return a COMMENT token (which would need
			// grammar support everywhere) but we instead collect
			// the comments in a side-channel list so that tools
			// like the code formatter can re-attach them by pos.
			lp := yylex.cast()
			lp.comments = append(lp.comments, &interfaces.Comment{
				Value: lval.str,
				Row:   yylex.Line(),
				Col:   yylex.Column(),
			})
		}
/./		{
			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 (
	"bufio"
	"io"
	"fmt"
	"strings"
	"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.
