Replace GOCC parser-generator with PGPG (#2015)

* Porting

* Update some tests that depend on AST-print output

* iterating on GOCC -> PGPG

* iterating

* iterating

* iterating

* iterating

* iterating

* Modify expout files that need only AST-print-syntax updates

* iterating

* iterating

* iterating

* iterating

* iterating

* iterating

* iterating

* iterating

* iterating

* iterating

* iterating

* sync test cases from mlr-6.17.0, except AST-prints ...

* sync test cases from mlr-6.17.0, except AST-prints ...

* Fix lashed emit for $* and @*

* Fix --1 and ++1 chained unary ops

* Emit with function callsite

* test cases

* dot operator

* M_PI and M_E

* Iterating on lashed emit cases

* error-wording differences

* rm some should-fail files

* Fix issue with leading semicolon

* trailing comma in func params; most AST-print deltas

* error-wording delta

* fix unset all

* AST-print deltas

* go mod tidy: forced `go 1.25` to `go 1.25.0`

* Depend on PGPG v1.0.0

* Fix Windows CI failure

* Remove cmd/experiments/dsl_parser

* GOCC -> PGPG

* neaten

* Fix regex issue found in doc gen
This commit is contained in:
John Kerl 2026-03-15 22:28:57 -04:00 committed by GitHub
parent 40f3a72de2
commit af1adf80ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
732 changed files with 924001 additions and 1122928 deletions

View file

@ -1 +0,0 @@
map \f :w<C-m>:!clear;echo Building ...; echo; build; echo; main<C-m>

View file

@ -1 +0,0 @@
Running the GOCC parser-generator for the full Miller grammar takes a few minutes. That's a bit painful for experimentation; hence this.

View file

@ -1,62 +0,0 @@
#!/bin/bash
# ----------------------------------------------------------------
# Setup
us=$(basename $0)
set -euo pipefail
verbose="false"
if [ $# -eq 1 ]; then
bnf="$1"
elif [ $# -eq 2 ]; then
if [ "$1" = "-v" ]; then
verbose="true"
else
echo "Usage: $0 {.bnf file}" 1>&2
exit 1
fi
bnf="$2"
else
echo "Usage: $0 {.bnf file}" 1>&2
exit 1
fi
dir=src
mkdir -p $dir
# ----------------------------------------------------------------
# Run the parser-generator
# Build the bin/gocc executable (use my fork for performance):
# get github.com/goccmack/gocc
go get github.com/johnkerl/gocc
bingocc="$GOPATH/bin/gocc"
if [ ! -x "$bingocc" ]; then
exit 1
fi
rm -f $dir/*.txt
if [ "$verbose" = "true" ]; then
lr1="$dir/LR1_conflicts.txt"
# The -o specifies the package name within the autogen
$bingocc -v -o $dir $bnf || expand -2 $lr1
else
$bingocc -o $dir $bnf
fi
echo "Parser-autogen OK"
# Code-gen directories:
# $dir/errors/
# $dir/lexer/
# $dir/parser/
# $dir/token/
# $dir/util/
# ----------------------------------------------------------------
# Compile the main and the parser-autogen
go build main.go
echo "Compile OK"

View file

@ -1,5 +0,0 @@
module one
go 1.24
toolchain go1.24.5

View file

@ -1,74 +0,0 @@
package main
import (
"fmt"
"os"
"one/src/lexer"
"one/src/parser"
)
func parseOne(input string) {
theLexer := lexer.NewLexer([]byte(input))
theParser := parser.NewParser()
_, err := theParser.Parse(theLexer)
green := "\033[32;01m"
red := "\033[31;01m"
textdefault := "\033[0m"
if err != nil {
//fmt.Println(err)
fmt.Printf("%sFail%s %s\n", red, textdefault, input)
} else {
fmt.Printf("%sOK%s %s\n", green, textdefault, input)
}
}
func main() {
if len(os.Args) == 1 {
fmt.Println("EXPECT OK")
goods := []string{
"",
";",
";;",
"x",
"x;x",
"x;x;x",
"x;x;x;x",
"x;",
"x;;",
";x",
";;x",
"x ; {}",
"{} ; x",
"{} x",
"{ x }",
"{ x; x }",
"x; { x; x }",
"{ x; x } x",
"{ x; x } ; x",
"{};{}",
"{} {}",
}
for _, input := range goods {
parseOne(input)
}
fmt.Println()
fmt.Println("EXPECT FAIL")
bads := []string{
"x x",
"x {}",
}
for _, input := range bads {
parseOne(input)
}
} else {
for _, arg := range os.Args[1:] {
parseOne(arg)
}
}
}

View file

@ -1,9 +0,0 @@
#!/bin/bash
if [ $# -lt 1 ]; then
echo "Usage: $0 {.bnf file} [expressions]" 1>&2
exit 1
fi
bnf="$1"
shift
./build $bnf && echo && main "$@"

View file

@ -1,29 +0,0 @@
!whitespace : ' ' | '\t' | '\n' | '\r' ;
Root
: StatementBlock
;
StatementBlock
: ";"
| BracelessStatement
| ";" BracelessStatement
| BracefulStatement
| BracelessStatement ";" NonEmptyStatementBlock
| BracefulStatement NonEmptyStatementBlock
;
NonEmptyStatementBlock
: BracelessStatement
| BracelessStatement ";" StatementBlock
| BracefulStatement
| BracefulStatement StatementBlock
;
BracelessStatement
: "x"
;
BracefulStatement
: "{" StatementBlock "}"
;

View file

@ -1,25 +0,0 @@
!whitespace : ' ' | '\t' | '\n' | '\r' ;
Root
: StatementBlock
;
StatementBlock
: empty
| ";" StatementBlock
| BracelessStatement
| BracefulStatement
| BracelessStatement ";" StatementBlock
| BracefulStatement ";" BracefulStatement
| BracefulStatement BracefulStatement
| BracefulStatement ";" BracelessStatement
| BracefulStatement BracelessStatement
;
BracelessStatement
: "x"
;
BracefulStatement
: "{" StatementBlock "}"
;

View file

@ -1,42 +0,0 @@
// ================================================================
// LEXER
_string_literal_element
: 'A'-'Z' | 'a'-'z' | '0'-'9'
| ' ' | '!' | '#' | '$' | '%' | '&' | '\'' | '\\'
| '(' | ')' | '*' | '+' | ',' | '-' | '.' | '/'
| ':' | ';' | '<' | '=' | '>' | '?' | '@' | '['
| ']' | '^' | '_' | '`' | '{' | '|' | '}' | '~'
| ( '\\' '"' )
| ( '\\' '[' )
| ( '\\' ']' )
| ( '\\' 'b' )
| ( '\\' 'f' )
| ( '\\' 'n' )
| ( '\\' 'r' )
| ( '\\' 't' )
| ( '\\' '\\' )
| ( '\\' '0' )
| ( '\\' 'x' )
| '\u0100'-'\U0010FFFF'
;
string_literal : '"' {_string_literal_element} '"' ;
// ================================================================
// IMPORT
<< import "two/src/dsl" >>
// ================================================================
// PARSER
// ----------------------------------------------------------------
Root
: StringLiteral
<< dsl.NewASTWithErrorReturn($0) >>
;
StringLiteral
: string_literal
<< dsl.NewASTNodeStripDoubleQuotePair($0, dsl.NodeTypeStringLiteral) >>
;

View file

@ -1,81 +0,0 @@
#!/bin/bash
# ----------------------------------------------------------------
# Setup
us=$(basename $0)
set -euo pipefail
verbose="false"
if [ $# -eq 1 ]; then
bnf="$1"
elif [ $# -eq 2 ]; then
if [ "$1" = "-v" ]; then
verbose="true"
else
echo "Usage: $0 {.bnf file}" 1>&2
exit 1
fi
bnf="$2"
else
echo "Usage: $0 {.bnf file}" 1>&2
exit 1
fi
dir=src/parsing
mkdir -p $dir
# ----------------------------------------------------------------
# Run the parser-generator
# Build the bin/gocc executable (use my fork for performance):
# go get github.com/goccmack/gocc
go get github.com/johnkerl/gocc
bingocc="$GOPATH/bin/gocc"
if [ ! -x "$bingocc" ]; then
exit 1
fi
rm -f $dir/*.txt
if [ "$verbose" = "true" ]; then
lr1="$dir/LR1_conflicts.txt"
# The -o specifies the package name within the autogen
$bingocc -v -o $dir $bnf || expand -2 $lr1
else
$bingocc -o $dir $bnf
fi
echo "Parser-autogen OK"
# Code-gen directories:
# $dir/errors/
# $dir/lexer/
# $dir/parser/
# $dir/token/
# $dir/util/
# ----------------------------------------------------------------
# Override GOCC codegen with customized error handling
cp ../../../go/src/parsing/errors.go.template src/parsing/errors/errors.go
sed -i .bak 's:miller/src:two/src:' src/parsing/errors/errors.go
# ----------------------------------------------------------------
# Copy AST files from the main Miller tree
rm -rf ./src/lib/
rm -rf ./src/dsl/
mkdir -p ./src/lib/
mkdir -p ./src/dsl/
cp ../../../go/src/lib/*.go ./src/lib/
cp ../../../go/src/dsl/ast*.go ./src/dsl/
# Different path to autogen between main Miller tree and here
sed -i .bak 's:miller/src:two/src:' src/dsl/ast*go
# ----------------------------------------------------------------
# Compile the main and the parser-autogen
go build main.go
echo "Compile OK"

View file

@ -1,147 +0,0 @@
// ================================================================
// LEXER
!whitespace : ' ' | '\t' | '\n' | '\r' ;
!comment : '#' {.} '\n' ;
_letter : 'a'-'z' | 'A'-'Z' ;
_decdig : '0'-'9' ;
_idchar : _letter | _decdig | '_' ;
emit : 'e' 'm' 'i' 't' ;
// ================================================================
// IMPORT
<< import "miller/src/dsl" >>
// ================================================================
// PARSER
// ----------------------------------------------------------------
Root
: EmitStatement
<< dsl.NewASTWithErrorReturn($0) >>
;
// ----------------------------------------------------------------
// Examples:
// emit @a
// emit (@a, @b)
// emit @a, "x", "y"
// emit (@a, @b), "x", "y"
// First argument (single or in parentheses) must be non-indexed
// oosvar/localvar/fieldname, so we can use their names as keys in the emitted
// record. These restrictions are enforced in the CST logic, to keep this
// parser/AST logic simpler.
EmitStatement
: emit Rvalue
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$1,
}
) >>
| emit Rvalue "," EmitArgs
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$1,
$3,
}
) >>
| emit "(" EmitArgs ")"
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$2,
}
) >>
| emit "(" EmitArgs ")" "," EmitArgs
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$2,
$5,
}
) >>
;
// ----------------------------------------------------------------
EmitArgs
: Rvalue
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeFunctionCallsite,
[]interface{}{
$0,
}
) >>
| Rvalue "," EmitArgs
<< dsl.WithChildPrepended(
$2,
$0,
) >>
;
// ----------------------------------------------------------------
FcnArgs
: Rvalue
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeFunctionCallsite,
[]interface{}{
$0,
}
) >>
// Allow trailing final comma, especially for multiline statements
| Rvalue ","
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeFunctionCallsite,
[]interface{}{
$0,
}
) >>
// Allow trailing final comma, especially for multiline statements
| Rvalue "," FcnArgs
<< dsl.WithChildPrepended(
$2,
$0,
) >>
;
// ----------------------------------------------------------------
Rvalue
: Literal
| "(" Literal ")"
;
Literal
: "x" << dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeStringLiteral,
[]interface{}{},
) >>
| "y" << dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeStringLiteral,
[]interface{}{},
) >>
| "z" << dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeStringLiteral,
[]interface{}{},
) >>
;

View file

@ -1,176 +0,0 @@
// ================================================================
// LEXER
!whitespace : ' ' | '\t' | '\n' | '\r' ;
!comment : '#' {.} '\n' ;
_letter : 'a'-'z' | 'A'-'Z' ;
_decdig : '0'-'9' ;
_idchar : _letter | _decdig | '_' ;
emit : 'e' 'm' 'i' 't' ;
// ================================================================
// IMPORT
<< import "two/src/dsl" >>
// ================================================================
// PARSER
// ----------------------------------------------------------------
Root
: EmitStatement
<< dsl.NewASTWithErrorReturn($0) >>
;
// ----------------------------------------------------------------
// Examples:
// emit @a
// emit (@a, @b)
// emit @a, "x", "y"
// emit (@a, @b), "x", "y"
// First argument (single or in parentheses) must be non-indexed
// oosvar/localvar/fieldname, so we can use their names as keys in the emitted
// record. These restrictions are enforced in the CST logic, to keep this
// parser/AST logic simpler.
EmitStatement
: emit Emittable
<<
dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$1,
},
)
>>
| emit "(" EmittableList ")"
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$2,
},
) >>
| emit Emittable "," EmitKeys
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$1,
$3,
},
) >>
| emit "(" EmittableList ")" "," EmitKeys
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$2,
$5,
},
) >>
;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmittableList
: Emittable
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeEmittableList,
[]interface{}{
$0,
}
) >>
// Allow trailing final comma, especially for multiline statements
| Emittable "," EmittableList
<< dsl.WithChildPrepended(
$2,
$0,
) >>
;
Emittable
: Literal
;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmitKeys
: Rvalue
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeEmitKeys,
[]interface{}{
$0,
},
) >>
| Rvalue "," EmitKeys
<< dsl.WithChildPrepended(
$2,
$0,
) >>
;
// ----------------------------------------------------------------
Rvalue
: Literal
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStringLiteral,
[]interface{}{
$0,
},
) >>
| "(" Literal ")"
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStringLiteral,
[]interface{}{
$1,
},
) >>
| "[" Literal "]"
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStringLiteral,
[]interface{}{
$1,
},
)) >>
| "[" Literal "," Literal "]"
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStringLiteral,
[]interface{}{
$1,
$2,
},
)) >>
;
Literal
: "x" << dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeStringLiteral,
[]interface{}{},
) >>
| "y" << dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeStringLiteral,
[]interface{}{},
) >>
| "z" << dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeStringLiteral,
[]interface{}{},
)) >>
;

View file

@ -1,5 +0,0 @@
module two
go 1.24
toolchain go1.24.5

View file

@ -1,123 +0,0 @@
package main
import (
"fmt"
"os"
"two/src/dsl"
"two/src/parsing/lexer"
"two/src/parsing/parser"
)
const GREEN = "\033[32;01m"
const RED = "\033[31;01m"
const TEXTDEFAULT = "\033[0m"
func parseOne(input string, printError bool) bool {
theLexer := lexer.NewLexer([]byte(input))
theParser := parser.NewParser()
iast, err := theParser.Parse(theLexer)
if err == nil {
fmt.Printf("%sOK%s %s\n", GREEN, TEXTDEFAULT, input)
iast.(*dsl.AST).Print()
fmt.Println()
return true
}
if printError {
fmt.Println(err)
}
fmt.Printf("%sFail%s %s\n", RED, TEXTDEFAULT, input)
fmt.Println()
return false
}
func main() {
printError := false
args := os.Args[1:] // os.Args[0] is program name in Go
if len(args) >= 1 && args[0] == "-v" {
printError = true
args = args[1:]
}
if len(args) == 0 {
ok := true
fmt.Println("----------------------------------------------------------------")
fmt.Println("EXPECT OK")
goods := []string{
"",
";",
";;",
";;;",
"x",
"x;x",
"x;x;x",
"x;x;x;x",
"x;",
"x;;",
";x",
";;x",
"x ; {}",
"{} ; x",
"{} x",
"{ x }",
"{ x; x }",
"x; { x; x }",
"{ x; x } x",
"{ x; x } ; x",
"{};{}",
"{} {}",
"{} {};",
"{};{};",
"{} {} {}",
"{};{}; {}",
"{} ; ; {}",
"x; x;",
"{x; x;}",
"{x; x;} x",
"{x; x;} x;",
"{x; x;} {};",
"{} x; {}",
"{} x; {};",
"{} x; x; {}",
"{} x; x; {};",
"{} x; x; x; {}",
"{} x; x; x; {};",
"{} {} ;;; {;;} x; x; x; x; x; {}",
}
for _, input := range goods {
if parseOne(input, printError) == false {
ok = false
}
}
fmt.Println()
fmt.Println("----------------------------------------------------------------")
fmt.Println("EXPECT FAIL")
bads := []string{
"x x",
"x {}",
}
for _, input := range bads {
if parseOne(input, printError) == true {
ok = false
}
}
fmt.Println()
fmt.Println("----------------------------------------------------------------")
if ok {
fmt.Printf("%sALL AS EXPECTED%s\n", GREEN, TEXTDEFAULT)
} else {
fmt.Printf("%sNOT ALL AS EXPECTED%s\n", RED, TEXTDEFAULT)
os.Exit(1)
}
} else {
for _, arg := range args {
parseOne(arg, printError)
}
}
}

View file

@ -1,15 +0,0 @@
#!/bin/bash
set -euo pipefail
# ~/.ctags needs to look like
# --langdef=Go
# --langmap=Go:.go
# --regex-Go=/func([ \t]+\([^)]+\))?[ \t]+([a-zA-Z0-9_]+)/\2/d,func/
# --regex-Go=/var[ \t]+([a-zA-Z_][a-zA-Z0-9_]+)/\1/d,var/
# --regex-Go=/type[ \t]+([a-zA-Z_][a-zA-Z0-9_]+)/\1/d,type/
#
# See also https://stackoverflow.com/questions/8204367/ctag-database-for-go
ctags -f gosource.tags -R `pwd`
mv gosource.tags tags

View file

@ -1,9 +0,0 @@
#!/bin/bash
if [ $# -lt 1 ]; then
echo "Usage: $0 {.bnf file} [expressions]" 1>&2
exit 1
fi
bnf="$1"
shift
./build $bnf && echo && main "$@"

View file

@ -1,127 +0,0 @@
// ================================================================
// LEXER
!whitespace : ' ' | '\t' | '\n' | '\r' ;
// ================================================================
// IMPORT
<< import "two/src/dsl" >>
// ================================================================
// PARSER
// ----------------------------------------------------------------
Root
: StatementBlock
<< dsl.NewASTWithErrorReturn($0) >>
;
// ================================================================
StatementBlock
// Empty statement. This allows for 'mlr put ""', as well as repeated semicolons.
: empty
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStatementBlock,
[]interface{}{},
) >>
| NonEmptyStatementBlock
<< dsl.WithErrorReturn($0) >>
;
// ----------------------------------------------------------------
NonEmptyStatementBlock
// ---------------------- Terminal rules
// Things not ending in a curly brace, like assignments -- and also do-while.
: BracelessStatement
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStatementBlock,
[]interface{}{
$0,
},
) >>
// Things ending in a curly brace, like for/do/while, begin/end, and pattern-action blocks
| BracefulStatement
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStatementBlock,
[]interface{}{
$0,
},
) >>
// ---------------------- Recursive rules
// So statements can start with a semicolon
| ";" StatementBlock
<< dsl.NewASTNodeWithErrorReturn(
// Normal case for sequential statements like '$x=1; $y=2'
| BracelessStatement ";" StatementBlock
<<dsl.WithChildPrepended($2, $0) >>
// For 'begin {...} ; $x=1'
| BracefulStatement ";" StatementBlock
<<dsl.WithChildPrepended($2, $0) >>
// These are for things like 'begin {...} begin {...} ...' -- where people
// shouldn't have to put semicolons after the closing curly braces.
//
// We get LR-1 conflicts with the following, so we need a pair of more
// explicit lookahead-by-more production rules instead. (By using ternaries
// we are effectively getting lookahead-by-two.)
//
// | BracefulStatement StatementBlock
// <<dsl.WithChildPrepended($1, $0) >>
// E.g. 'begin {...} begin {...} $x=1'
| BracefulStatement BracefulStatement StatementBlock
<<dsl.WithTwoChildrenPreprended($2, $0, $1) >>
// E.g. 'begin {...} $x=1'
| BracefulStatement BracelessStatement
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStatementBlock,
[]interface{}{
$0,
$1,
},
) >>
// E.g. 'begin {...} $x=1 ;'
| BracefulStatement BracelessStatement ";"
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStatementBlock,
[]interface{}{
$0,
$1,
},
) >>
| BracefulStatement BracelessStatement ";" NonEmptyStatementBlock
<<dsl.WithTwoChildrenPreprended($3, $0, $1) >>
;
// ----------------------------------------------------------------
BracelessStatement
: "x"
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeBareBoolean,
[]interface{}{},
) >>
;
// ----------------------------------------------------------------
BracefulStatement
: "{" StatementBlock "}"
<< dsl.NewASTNodeWithErrorReturn(
;

View file

@ -1,302 +0,0 @@
// ================================================================
// LEXER
!whitespace : ' ' | '\t' | '\n' | '\r' ;
!comment : '#' {.} '\n' ;
_letter : 'a'-'z' | 'A'-'Z' ;
_decdig : '0'-'9' ;
_idchar : _letter | _decdig | '_' ;
emitf : 'e' 'm' 'i' 't' 'f';
emit : 'e' 'm' 'i' 't' ;
stdout : 's' 't' 'd' 'o' 'u' 't' ;
stderr : 's' 't' 'd' 'e' 'r' 'r' ;
// ================================================================
// IMPORT
<< import "two/src/dsl" >>
// ================================================================
// PARSER
// ----------------------------------------------------------------
Root
: EmitFStatement
<< dsl.NewASTWithErrorReturn($0) >>
| EmitStatement
<< dsl.NewASTWithErrorReturn($0) >>
;
// ----------------------------------------------------------------
Redirector
: ">" RedirectTarget
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeRedirectWrite,
[]interface{}{
$1,
}
) >>
| ">>" RedirectTarget
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeRedirectAppend,
[]interface{}{
$1,
}
) >>
| "|" RedirectTarget
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeRedirectPipe,
[]interface{}{
$1,
}
) >>
;
RedirectTarget
: stdout
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeRedirectTargetStdout,
[]interface{}{}
) >>
| stderr
<< dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeRedirectTargetStderr,
[]interface{}{}
) >>
| Rvalue
;
// ----------------------------------------------------------------
EmitStatement
: emit Emittable
<<
dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$1,
dsl.NewASTNodeTerminal(nil, dsl.NodeTypeNoOp),
dsl.NewASTNodeTerminal(nil, dsl.NodeTypeNoOp),
}
))
>>
| emit Redirector "," Emittable
<<
dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$3,
dsl.NewASTNodeTerminal(nil, dsl.NodeTypeNoOp),
$1,
}
))
>>
| emit "(" EmittableList ")"
<<
dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$2,
dsl.NewASTNodeTerminal(nil, dsl.NodeTypeNoOp),
dsl.NewASTNodeTerminal(nil, dsl.NodeTypeNoOp),
}
))
>>
| emit Redirector "," "(" EmittableList ")"
<<
dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$4,
dsl.NewASTNodeTerminal(nil, dsl.NodeTypeNoOp),
$1,
}
))
>>
| emit Emittable "," EmitKeys
<<
dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$1,
$3,
dsl.NewASTNodeTerminal(nil, dsl.NodeTypeNoOp),
}
))
>>
| emit Redirector "," Emittable "," EmitKeys
<<
dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$3,
$5,
$1,
}
))
>>
| emit "(" EmittableList ")" "," EmitKeys
<<
dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$2,
$5,
dsl.NewASTNodeTerminal(nil, dsl.NodeTypeNoOp),
}
))
>>
| emit Redirector "," "(" EmittableList ")" "," EmitKeys
<<
dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeEmitStatement,
[]interface{}{
$4,
$7,
$1,
}
))
>>
;
// ----------------------------------------------------------------
// Examples:
// emitf @a
// emitf @a, b, $c
// Each argument must be a non-indexed oosvar/localvar/fieldname, so we can use
// their names as keys in the emitted record.
EmitFStatement
: emitf EmittableList
<< dsl.WithChildrenAdopted(
dsl.NewASTNodeTerminal(
$0,
dsl.NodeTypeEmitFStatement,
),
$1,
) >>
| emitf Redirector "," EmittableList
// TODO
;
// ----------------------------------------------------------------
EmittableList
: Emittable
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeEmittableList,
[]interface{}{
$0,
}
) >>
// Allow trailing final comma, especially for multiline statements
| Emittable "," EmittableList
<< dsl.WithChildPrepended(
$2,
$0,
) >>
;
Emittable
: Literal
;
// ----------------------------------------------------------------
EmitKeys
: Rvalue
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeEmitKeys,
[]interface{}{
$0,
}
) >>
| Rvalue "," EmitKeys
<< dsl.WithChildPrepended(
$2,
$0,
) >>
;
// ----------------------------------------------------------------
Rvalue
: Literal
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStringLiteral,
[]interface{}{
$0,
}
) >>
| "(" Literal ")"
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStringLiteral,
[]interface{}{
$1,
}
) >>
| "[" Literal "]"
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStringLiteral,
[]interface{}{
$1,
}
) >>
| "[" Literal "," Literal "]"
<< dsl.NewASTNodeWithErrorReturn(
nil,
dsl.NodeTypeStringLiteral,
[]interface{}{
$1,
$2,
}
) >>
;
Literal
: "x" << dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeStringLiteral,
[]interface{}{}
) >>
| "y" << dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeStringLiteral,
[]interface{}{}
) >>
| "z" << dsl.NewASTNodeWithErrorReturn(
$0,
dsl.NodeTypeStringLiteral,
[]interface{}{}
) >>
;