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

@ -94,15 +94,12 @@ So, in broad overview, the key packages are:
### Dependencies
* Miller dependencies are all in the Go standard library, except two:
* GOCC lexer/parser code-generator from [github.com/goccmack/gocc](https://github.com/goccmack/gocc):
* Forked at [github.com/johnkerl/gocc](github.com/johnkerl/gocc).
* This package defines the grammar for Miller's domain-specific language (DSL) for the Miller `put` and `filter` verbs. And, GOCC is a joy to use. :)
* It is used on the terms of its open-source license.
* PGPG lexer/parser code-generator from [github.com/johnkerl/pgpg](https://github.com/johnkerl/pgpg):
* This package defines the grammar for Miller's domain-specific language (DSL) for the Miller `put` and `filter` verbs.
* [golang.org/x/term](https://pkg.go.dev/golang.org/x/term):
* Just a one-line Miller callsite for is-a-terminal checking for the [Miller REPL](./pkg/terminals/repl/README.md).
* It is used on the terms of its open-source license.
* See also [./go.mod](go.mod). Setup:
* `go get github.com/johnkerl/gocc`
* `go get golang.org/x/term`
### Miller per se
@ -120,9 +117,9 @@ So, in broad overview, the key packages are:
* [pkg/input](./pkg/input) is as above -- one record-reader type per supported input file format, and a factory method.
* [pkg/output](./pkg/output) is as above -- one record-writer type per supported output file format, and a factory method.
* [pkg/transformers](./pkg/transformers) contains the abstract record-transformer interface datatype, as well as the Go-channel chaining mechanism for piping one transformer into the next. It also contains all the concrete record-transformers such as `cat`, `tac`, `sort`, `put`, and so on.
* [pkg/parsing](./pkg/parsing) contains a single source file, `mlr.bnf`, which is the lexical/semantic grammar file for the Miller `put`/`filter` DSL using the GOCC framework. All subdirectories of `pkg/parsing/` are autogen code created by GOCC's processing of `mlr.bnf`. If you need to edit `mlr.bnf`, please use [tools/build-dsl](./tools/build-dsl) to autogenerate Go code from it (using the GOCC tool). (This takes several minutes to run.) See also [tools/format-go-in-bnf](./tools/format-go-in-bnf) (which reads `stdin` and writes `stdout`) for automated formatting of the Go bits.
* [pkg/dsl](./pkg/dsl) contains [`ast_types.go`](pkg/dsl/ast_types.go) which is the abstract syntax tree datatype shared between GOCC and Miller. I didn't use a `pkg/dsl/ast` naming convention, although that would have been nice, in order to avoid a Go package-dependency cycle.
* [pkg/dsl/cst](./pkg/dsl/cst) is the concrete syntax tree, constructed from an AST produced by GOCC. The CST is what is actually executed on every input record when you do things like `$z = $x * 0.3 * $y`. Please see the [pkg/dsl/cst/README.md](./pkg/dsl/cst/README.md) for more information.
* [pkg/parsing](./pkg/parsing) contains a single source file, `mlr.bnf`, which is the lexical/semantic grammar file for the Miller `put`/`filter` DSL using the PGPG framework. All subdirectories of `pkg/parsing/` are autogen code created by PGPG's processing of `mlr.bnf`. If you need to edit `mlr.bnf`, please use [tools/build-dsl](./tools/build-dsl) to autogenerate Go code from it (using the PGPG tool). (This takes several minutes to run.) See also [tools/format-go-in-bnf](./tools/format-go-in-bnf) (which reads `stdin` and writes `stdout`) for automated formatting of the Go bits.
* [pkg/dsl](./pkg/dsl) contains [`ast_types.go`](pkg/dsl/ast_types.go) which is the abstract syntax tree datatype shared between PGPG and Miller. I didn't use a `pkg/dsl/ast` naming convention, although that would have been nice, in order to avoid a Go package-dependency cycle.
* [pkg/dsl/cst](./pkg/dsl/cst) is the concrete syntax tree, constructed from an AST produced by PGPG. The CST is what is actually executed on every input record when you do things like `$z = $x * 0.3 * $y`. Please see the [pkg/dsl/cst/README.md](./pkg/dsl/cst/README.md) for more information.
## Nil-record conventions

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{}{}
) >>
;

View file

@ -3820,5 +3820,5 @@ This is simply a copy of what you should see on running `man mlr` at a command p
MIME Type for Comma-Separated Values (CSV) Files, the Miller docsite
https://miller.readthedocs.io
2026-03-06 4mMILLER24m(1)
2026-03-16 4mMILLER24m(1)
</pre>

View file

@ -3799,4 +3799,4 @@
MIME Type for Comma-Separated Values (CSV) Files, the Miller docsite
https://miller.readthedocs.io
2026-03-06 4mMILLER24m(1)
2026-03-16 4mMILLER24m(1)

View file

@ -64,12 +64,7 @@ error.
</pre>
<pre class="pre-non-highlight-in-pair">
mlr: cannot parse DSL expression.
mlr: Parse error on token "$y" at line 6 column 3.
Please check for missing semicolon.
Expected one of:
␚ ; > >> | ? || ^^ && =~ !=~ == != <=> >= < <= ^ & << >>> + - .+ .- *
/ // % .* ./ .// . ?? ??? ** [
mlr: parse error: unexpected field_name ("$y")
</pre>
## elif

View file

@ -776,12 +776,7 @@ As of September 2021, immediately invoked function expressions (IIFEs) are not p
</pre>
<pre class="pre-non-highlight-in-pair">
mlr: cannot parse DSL expression.
mlr: Parse error on token "(" at line 4 column 35.
Please check for missing semicolon.
Expected one of:
; } > >> | ? || ^^ && =~ !=~ == != <=> >= < <= ^ & << >>> + - .+ .- * /
// % .* ./ .// . ?? ??? **
mlr: parse error: unexpected lparen ("(")
</pre>
but this does:

View file

@ -457,7 +457,7 @@ to files.
Keep in mind that out-of-stream variables are a nested, multi-level [map](reference-main-maps.md) (directly viewable as JSON using `dump`), while Miller record values are as well during processing -- but records may be flattened down for output to tabular formats. See the page [Flatten/unflatten: JSON vs. tabular formats](flatten-unflatten.md) for more information.
* You can use `emit1` to emit any map-valued expression, including `$*`, map-valued out-of-stream variables, the entire out-of-stream-variable collection `@*`, map-valued local variables, map literals, or map-valued function return values.
* For `emit`, `emitp`, and `emitf`, you can emit map-valued local variables, map-valued field attributes (with `$`), map-va out-of-stream variables (with `@`), `$*`, `@*`, or map literals (with outermost `{...}`) -- but not arbitrary expressions which evaluate to map (such as function return values).
* For `emit`, `emitp`, and `emitf`, you can emit map-valued local variables, map-valued field attributes (with `$`), map-valued out-of-stream variables (with `@`), `$*`, `@*`, map literals (with outermost `{...}`), or map-valued function return values (e.g. `emit f($a, $b)`).
The reason for this is partly historical and partly technical. As we'll see below, you can do lots of syntactical things with `emit`, `emitp`, and `emitf`, including printing them side-by-side, indexing them, redirecting the output to files, etc. What this means syntactically is that Miller's parser needs to handle all sorts of commas, parentheses, and so on:

View file

@ -209,7 +209,7 @@ to files.
Keep in mind that out-of-stream variables are a nested, multi-level [map](reference-main-maps.md) (directly viewable as JSON using `dump`), while Miller record values are as well during processing -- but records may be flattened down for output to tabular formats. See the page [Flatten/unflatten: JSON vs. tabular formats](flatten-unflatten.md) for more information.
* You can use `emit1` to emit any map-valued expression, including `$*`, map-valued out-of-stream variables, the entire out-of-stream-variable collection `@*`, map-valued local variables, map literals, or map-valued function return values.
* For `emit`, `emitp`, and `emitf`, you can emit map-valued local variables, map-valued field attributes (with `$`), map-va out-of-stream variables (with `@`), `$*`, `@*`, or map literals (with outermost `{...}`) -- but not arbitrary expressions which evaluate to map (such as function return values).
* For `emit`, `emitp`, and `emitf`, you can emit map-valued local variables, map-valued field attributes (with `$`), map-valued out-of-stream variables (with `@`), `$*`, `@*`, map literals (with outermost `{...}`), or map-valued function return values (e.g. `emit f($a, $b)`).
The reason for this is partly historical and partly technical. As we'll see below, you can do lots of syntactical things with `emit`, `emitp`, and `emitf`, including printing them side-by-side, indexing them, redirecting the output to files, etc. What this means syntactically is that Miller's parser needs to handle all sorts of commas, parentheses, and so on:

View file

@ -383,7 +383,8 @@ ASCII character classes:
[[:word:]] word characters (== [0-9A-Za-z_])
[[:xdigit:]] hex digit (== [0-9A-Fa-f])
Unicode character classes are those in unicode.Categories and unicode.Scripts.
Unicode character classes are those in unicode.Categories,
unicode.CategoryAliases, and unicode.Scripts.
func IsWordChar(r rune) bool
type EmptyOp uint8

1
go.mod
View file

@ -19,6 +19,7 @@ go 1.25.0
require (
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb
github.com/johnkerl/lumin v1.0.0
github.com/johnkerl/pgpg/go v1.0.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.18.4
github.com/kshedden/statmodel v0.0.0-20210519035403-ee97d3e48df1

2
go.sum
View file

@ -15,6 +15,8 @@ github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8I
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/johnkerl/lumin v1.0.0 h1:CV34cHZOJ92Y02RbQ0rd4gA0C06Qck9q8blOyaPoWpU=
github.com/johnkerl/lumin v1.0.0/go.mod h1:eLf5AdQOaLvzZ2zVy4REr/DSeEwG+CZreHwNLICqv9E=
github.com/johnkerl/pgpg/go v1.0.0 h1:JG3Xiu9uatZmouiKvqZV2Ql6QmqV9Vw0t/GD/Dn+oj8=
github.com/johnkerl/pgpg/go v1.0.0/go.mod h1:Jh0kkya72bexXymdoZwWJPu6bEOTPLDa5V+gBkKVWL0=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=

View file

@ -3799,4 +3799,4 @@
MIME Type for Comma-Separated Values (CSV) Files, the Miller docsite
https://miller.readthedocs.io
2026-03-06 4mMILLER24m(1)
2026-03-16 4mMILLER24m(1)

View file

@ -2,12 +2,12 @@
.\" Title: mlr
.\" Author: [see the "AUTHOR" section]
.\" Generator: ./mkman.rb
.\" Date: 2026-03-06
.\" Date: 2026-03-16
.\" Manual: \ \&
.\" Source: \ \&
.\" Language: English
.\"
.TH "MILLER" "1" "2026-03-06" "\ \&" "\ \&"
.TH "MILLER" "1" "2026-03-16" "\ \&" "\ \&"
.\" -----------------------------------------------------------------
.\" * Portability definitions
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -4,7 +4,7 @@ Parsing a Miller DSL (domain-specific language) expression goes through three re
* Abstract syntax tree (AST)
* Concrete syntax tree (AST)
The job of the GOCC parser is to turn the DSL string into an AST.
The job of the PGPG parser is to turn the DSL string into an AST.
The job of the CST builder is to turn the AST into a CST.

View file

@ -1,198 +0,0 @@
// AST-build methods, for use by callbacks within the GOCC/BNF Miller
// DSL grammar in mlr.bnf.
package dsl
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/parsing/token"
)
// This is for the GOCC/BNF parser, which produces an AST
func NewASTWithErrorReturn(iroot interface{}) (*AST, error) {
return &AST{
RootNode: iroot.(*ASTNode),
}, nil
}
func NewASTNodeTerminal(itok interface{}, nodeType TNodeType) *ASTNode {
var tok *token.Token = nil
if itok != nil {
tok = itok.(*token.Token)
}
return &ASTNode{
Token: tok,
Type: nodeType,
Children: nil,
}
}
// NewASTNode is the ASTNode constructor. If children is non-nil and length 0, a
// zary node is created. (Example: a function call with zero arguments.) If
// children is nil, a terminal node is created. (Example: a string or integer
// literal.)
func NewASTNode(
itok interface{},
nodeType TNodeType,
children []interface{},
) *ASTNode {
var tok *token.Token = nil
if itok != nil {
tok = itok.(*token.Token)
}
node := &ASTNode{
Token: tok,
Type: nodeType,
Children: nil,
}
if children == nil {
return node
}
n := len(children)
node.Children = make([]*ASTNode, n)
for i, child := range children {
node.Children[i] = child.(*ASTNode)
}
return node
}
// Pass-through expressions in the grammar sometimes need to be turned from
// (ASTNode) to (ASTNode, error). This is for GOCC.
func WithErrorReturn(iparent interface{}) (*ASTNode, error) {
return iparent.(*ASTNode), nil
}
func NewASTNodeWithErrorReturn(
itok interface{},
nodeType TNodeType,
children []interface{},
) (*ASTNode, error) {
return WithErrorReturn(NewASTNode(itok, nodeType, children))
}
func NewASTNodeTerminalWithErrorReturn(itok interface{}, nodeType TNodeType) (*ASTNode, error) {
return WithErrorReturn(NewASTNode(itok, nodeType, nil))
}
// Strips the leading '$' from field names, or '@' from oosvar names. Not done
// in the parser itself due to LR-1 conflicts.
func NewASTNodeStripDollarOrAtSign(itok interface{}, nodeType TNodeType) (*ASTNode, error) {
oldToken := itok.(*token.Token)
lib.InternalCodingErrorIf(len(oldToken.Lit) < 2)
lib.InternalCodingErrorIf(oldToken.Lit[0] != '$' && oldToken.Lit[0] != '@')
newToken := &token.Token{
Type: oldToken.Type,
Lit: oldToken.Lit[1:],
Pos: oldToken.Pos,
}
return NewASTNodeTerminal(newToken, nodeType), nil
}
// Strips the leading '${' and trailing '}' from braced field names, or '@{'
// and '}' from oosvar names. Not done in the parser itself due to LR-1
// conflicts.
func NewASTNodeStripDollarOrAtSignAndCurlyBraces(
itok interface{},
nodeType TNodeType,
) (*ASTNode, error) {
oldToken := itok.(*token.Token)
n := len(oldToken.Lit)
lib.InternalCodingErrorIf(n < 4)
lib.InternalCodingErrorIf(oldToken.Lit[0] != '$' && oldToken.Lit[0] != '@')
lib.InternalCodingErrorIf(oldToken.Lit[1] != '{')
lib.InternalCodingErrorIf(oldToken.Lit[n-1] != '}')
newToken := &token.Token{
Type: oldToken.Type,
Lit: oldToken.Lit[2 : n-1],
Pos: oldToken.Pos,
}
return NewASTNodeTerminal(newToken, nodeType), nil
}
// Likewise for the leading/trailing double quotes on string literals. Also,
// since string literals can have backslash-escaped double-quotes like
// "...\"...\"...", we also unbackslash here.
func NewASTNodeStripDoubleQuotePair(
itok interface{},
nodeType TNodeType,
) (*ASTNode, error) {
oldToken := itok.(*token.Token)
n := len(oldToken.Lit)
contents := string(oldToken.Lit[1 : n-1])
newToken := &token.Token{
Type: oldToken.Type,
Lit: []byte(contents),
Pos: oldToken.Pos,
}
return NewASTNodeTerminal(newToken, nodeType), nil
}
func WithChildPrepended(iparent interface{}, ichild interface{}) (*ASTNode, error) {
parent := iparent.(*ASTNode)
child := ichild.(*ASTNode)
if parent.Children == nil {
parent.Children = []*ASTNode{child}
} else {
parent.Children = append([]*ASTNode{child}, parent.Children...)
}
return parent, nil
}
func WithTwoChildrenPreprended(iparent interface{}, ichildA, ichildB interface{}) (*ASTNode, error) {
parent := iparent.(*ASTNode)
childA := ichildA.(*ASTNode)
childB := ichildB.(*ASTNode)
if parent.Children == nil {
parent.Children = []*ASTNode{childA, childB}
} else {
parent.Children = append([]*ASTNode{childA, childB}, parent.Children...)
}
return parent, nil
}
func WithChildAppended(iparent interface{}, child interface{}) (*ASTNode, error) {
parent := iparent.(*ASTNode)
if parent.Children == nil {
parent.Children = []*ASTNode{child.(*ASTNode)}
} else {
parent.Children = append(parent.Children, child.(*ASTNode))
}
return parent, nil
}
func WithChildrenAdopted(iparent interface{}, ichild interface{}) (*ASTNode, error) {
parent := iparent.(*ASTNode)
child := ichild.(*ASTNode)
parent.Children = child.Children
child.Children = nil
return parent, nil
}
func (node *ASTNode) CheckArity(
arity int,
) error {
if len(node.Children) != arity {
return fmt.Errorf("expected AST node arity %d, got %d", arity, len(node.Children))
}
return nil
}
// Tokens are produced by GOCC. However there is an exception: for the ternary
// operator I want the AST to have a "?:" token, which GOCC doesn't produce
// since nothing is actually spelled like that in the DSL.
func NewASTToken(iliteral interface{}, iclonee interface{}) *token.Token {
literal := iliteral.(string)
clonee := iclonee.(*token.Token)
return &token.Token{
Type: clonee.Type,
Lit: []byte(literal),
Pos: clonee.Pos,
}
}

View file

@ -1,312 +0,0 @@
// Print routines for AST and ASTNode
package dsl
import (
"fmt"
"strings"
)
// Print is indent-style multiline print.
// Example, given parse of '$y = 2 * $x + 1':
//
// * statement block
// * assignment "="
// * direct field value "y"
// * operator "+"
// * operator "*"
// * int literal "2"
// * direct field value "x"
// * int literal "1"
func (ast *AST) Print() {
ast.RootNode.Print()
}
// PrintParex is parenthesized-expression print.
// Example, given parse of '$y = 2 * $x + 1':
//
// (statement-block
// (=
// $y
// (+
// (* 2 $x)
// 1
// )
// )
// )
func (ast *AST) PrintParex() {
ast.RootNode.PrintParex()
}
// PrintParexOneLine is parenthesized-expression print, all on one line.
// Example, given parse of '$y = 2 * $x + 1':
//
// (statement-block (= $y (+ (* 2 $x) 1)))
func (ast *AST) PrintParexOneLine() {
ast.RootNode.PrintParexOneLine()
}
// Print is indent-style multiline print.
func (node *ASTNode) Print() {
node.printAux(0)
}
// printAux is a recursion-helper for Print.
func (node *ASTNode) printAux(depth int) {
// Indent
for range depth {
fmt.Print(" ")
}
// Token text (if non-nil) and token type
tok := node.Token
fmt.Print("* " + node.Type)
if tok != nil {
fmt.Printf(" \"%s\"", string(tok.Lit))
}
fmt.Println()
// Children, indented one level further
for _, child := range node.Children {
child.printAux(depth + 1)
}
}
// PrintParex is parenthesized-expression print.
func (node *ASTNode) PrintParex() {
node.printParexAux(0)
}
// printParexAux is a recursion-helper for PrintParex.
func (node *ASTNode) printParexAux(depth int) {
if node.IsLeaf() {
for range depth {
fmt.Print(" ")
}
fmt.Println(node.Text())
} else if node.ChildrenAreAllLeaves() {
// E.g. (= sum 0) or (+ 1 2)
for range depth {
fmt.Print(" ")
}
fmt.Print("(")
fmt.Print(node.Text())
for _, child := range node.Children {
fmt.Print(" ")
fmt.Print(child.Text())
}
fmt.Println(")")
} else {
// Parent and opening parenthesis on first line
for range depth {
fmt.Print(" ")
}
fmt.Print("(")
fmt.Println(node.Text())
// Children on their own lines
for _, child := range node.Children {
child.printParexAux(depth + 1)
}
// Closing parenthesis on last line
for range depth {
fmt.Print(" ")
}
fmt.Println(")")
}
}
// PrintParexOneLine is parenthesized-expression print, all on one line.
func (node *ASTNode) PrintParexOneLine() {
node.printParexOneLineAux()
fmt.Println()
}
// printParexOneLineAux is a recursion-helper for PrintParexOneLine.
func (node *ASTNode) printParexOneLineAux() {
if node.IsLeaf() {
fmt.Print(node.Text())
} else {
fmt.Print("(")
fmt.Print(node.Text())
for _, child := range node.Children {
fmt.Print(" ")
child.printParexOneLineAux()
}
fmt.Print(")")
}
}
// IsLeaf determines if an AST node is a leaf node.
func (node *ASTNode) IsLeaf() bool {
return len(node.Children) == 0
}
// ChildrenAreAllLeaves determines if an AST node's children are all leaf nodes.
func (node *ASTNode) ChildrenAreAllLeaves() bool {
for _, child := range node.Children {
if !child.IsLeaf() {
return false
}
}
return true
}
// Text makes a human-readable, whitespace-free name for an AST node. Some
// nodes have non-nil tokens; other, nil. And token-types can have spaces in
// them. In this method we use custom mappings to always get a whitespace-free
// representation of the content of a single AST node.
func (node *ASTNode) Text() string {
tokenText := ""
if node.Token != nil {
tokenText = string(node.Token.Lit)
}
switch node.Type {
case NodeTypeStringLiteral:
return "\"" + strings.ReplaceAll(tokenText, "\"", "\\\"") + "\""
case NodeTypeIntLiteral:
return tokenText
case NodeTypeFloatLiteral:
return tokenText
case NodeTypeBoolLiteral:
return tokenText
case NodeTypeNullLiteral:
return tokenText
case NodeTypeArrayLiteral:
return tokenText
case NodeTypeMapLiteral:
return tokenText
case NodeTypeMapLiteralKeyValuePair:
return tokenText
case NodeTypeArrayOrMapIndexAccess:
return "[]"
case NodeTypeArraySliceAccess:
return "[:]"
case NodeTypeArraySliceEmptyLowerIndex:
return "array-slice-empty-lower-index"
case NodeTypeArraySliceEmptyUpperIndex:
return "array-slice-empty-upper-index"
case NodeTypeContextVariable:
return tokenText
case NodeTypeConstant:
return tokenText
case NodeTypeEnvironmentVariable:
return "ENV[\"" + tokenText + "\"]"
case NodeTypeDirectFieldValue:
return "$" + tokenText
case NodeTypeIndirectFieldValue:
return "$[" + tokenText + "]"
case NodeTypeFullSrec:
return tokenText
case NodeTypeDirectOosvarValue:
return "@" + tokenText
case NodeTypeIndirectOosvarValue:
return "@[" + tokenText + "]"
case NodeTypeFullOosvar:
return tokenText
case NodeTypeLocalVariable:
return tokenText
case NodeTypeTypedecl:
return tokenText
case NodeTypeStatementBlock:
return "statement-block"
case NodeTypeAssignment:
return tokenText
case NodeTypeUnset:
return tokenText
case NodeTypeBareBoolean:
return "bare-boolean"
case NodeTypeFilterStatement:
return tokenText
case NodeTypeEmit1Statement:
return tokenText
case NodeTypeEmitStatement:
return tokenText
case NodeTypeEmitPStatement:
return tokenText
case NodeTypeEmitFStatement:
return tokenText
case NodeTypeDumpStatement:
return tokenText
case NodeTypeEdumpStatement:
return tokenText
case NodeTypePrintStatement:
return tokenText
case NodeTypeEprintStatement:
return tokenText
case NodeTypePrintnStatement:
return tokenText
case NodeTypeEprintnStatement:
return tokenText
case NodeTypeNoOp:
return "no-op"
case NodeTypeOperator:
return tokenText
case NodeTypeDotOperator:
return tokenText
case NodeTypeFunctionCallsite:
return tokenText
case NodeTypeBeginBlock:
return "begin"
case NodeTypeEndBlock:
return "end"
case NodeTypeIfChain:
return "if-chain"
case NodeTypeIfItem:
return tokenText
case NodeTypeCondBlock:
return "cond"
case NodeTypeWhileLoop:
return tokenText
case NodeTypeDoWhileLoop:
return tokenText
case NodeTypeForLoopOneVariable:
return tokenText
case NodeTypeForLoopTwoVariable:
return tokenText
case NodeTypeForLoopMultivariable:
return tokenText
case NodeTypeTripleForLoop:
return tokenText
case NodeTypeBreak:
return tokenText
case NodeTypeContinue:
return tokenText
case NodeTypeNamedFunctionDefinition:
return "named-udf"
case NodeTypeUnnamedFunctionDefinition:
return "unnamed-udf"
case NodeTypeSubroutineDefinition:
return "subr"
case NodeTypeParameterList:
return "parameters"
case NodeTypeParameter:
return "parameter"
case NodeTypeParameterName:
return tokenText
case NodeTypeReturn:
return tokenText
case NodeTypePanic:
return tokenText
}
return "[ERROR]"
}

View file

@ -1,21 +1,9 @@
// AST and ASTNode data structures for the Miller DSL parser
// TNodeType constants for the Miller DSL parser.
// The CST builder uses asts.ASTNode from github.com/johnkerl/pgpg;
// these types identify node kinds when converting PGPG ASTs to CST nodes.
package dsl
import (
"github.com/johnkerl/miller/v6/pkg/parsing/token"
)
type AST struct {
RootNode *ASTNode
}
type ASTNode struct {
Token *token.Token // Nil for tokenless/structural nodes
Type TNodeType
Children []*ASTNode
}
type TNodeType string
const (

View file

@ -3,16 +3,18 @@
package cst
import (
"github.com/johnkerl/miller/v6/pkg/dsl"
"fmt"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
func (root *RootNode) BuildAssignmentNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (*AssignmentNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeAssignment)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeAssignment))
err := astNode.CheckArity(2)
if err != nil {
return nil, err
@ -37,6 +39,99 @@ func (root *RootNode) BuildAssignmentNode(
}, nil
}
func (root *RootNode) BuildCompoundAssignmentNode(
astNode *asts.ASTNode,
) (*AssignmentNode, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeCompoundAssignment))
err := astNode.CheckArity(3)
if err != nil {
return nil, err
}
lhsASTNode := astNode.Children[0]
opASTNode := astNode.Children[1]
rhsASTNode := astNode.Children[2]
compoundOp := tokenLit(opASTNode)
baseOp := compoundOpToBaseOp(compoundOp)
if baseOp == "" {
return nil, fmt.Errorf("unknown compound assignment operator: %s", compoundOp)
}
lvalueNode, err := root.BuildAssignableNode(lhsASTNode)
if err != nil {
return nil, err
}
lvalueAsRvalue, err := root.BuildEvaluableNode(lhsASTNode)
if err != nil {
return nil, err
}
rvalueNode, err := root.BuildEvaluableNode(rhsASTNode)
if err != nil {
return nil, err
}
compoundRvalueNode, err := root.buildBinaryOperatorFromEvaluables(baseOp, lvalueAsRvalue, rvalueNode, rhsASTNode)
if err != nil {
return nil, err
}
return &AssignmentNode{
lvalueNode: lvalueNode,
rvalueNode: compoundRvalueNode,
}, nil
}
// compoundOpToBaseOp maps compound assignment operators to their base operator.
// E.g. "+=" -> "+", "**=" -> "**".
func compoundOpToBaseOp(compound string) string {
switch compound {
case "||=":
return "||"
case "^^=":
return "^^"
case "&&=":
return "&&"
case "??=":
return "??"
case "???=":
return "???"
case "|=":
return "|"
case "&=":
return "&"
case "^=":
return "^"
case "<<=":
return "<<"
case ">>=":
return ">>"
case ">>>=":
return ">>>"
case "+=":
return "+"
case ".=":
return "."
case "-=":
return "-"
case "*=":
return "*"
case "/=":
return "/"
case "//=":
return "//"
case "%=":
return "%"
case "**=":
return "**"
default:
return ""
}
}
type AssignmentNode struct {
lvalueNode IAssignable
rvalueNode IEvaluable
@ -56,17 +151,24 @@ func (node *AssignmentNode) Execute(
}
func (root *RootNode) BuildUnsetNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (*UnsetNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeUnset)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeUnset))
lvalueNodes := make([]IAssignable, len(astNode.Children))
for i, lhsASTNode := range astNode.Children {
lvalueNode, err := root.BuildAssignableNode(lhsASTNode)
if err != nil {
return nil, err
var lvalueNode IAssignable
var err error
// "all" is a synonym for @* (full oosvar) in unset context.
if lhsASTNode.Type == asts.NodeType(NodeTypeLocalVariable) && tokenLit(lhsASTNode) == "all" {
lvalueNode = NewFullOosvarLvalueNode()
} else {
lvalueNode, err = root.BuildAssignableNode(lhsASTNode)
if err != nil {
return nil, err
}
}
lvalueNodes[i] = lvalueNode
}

153
pkg/dsl/cst/ast_types.go Normal file
View file

@ -0,0 +1,153 @@
// PGPG AST node type constants. These match the "type" field values
// produced by the mlr.bnf grammar. The CST uses these when
// comparing asts.ASTNode.Type (asts.NodeType is string).
package cst
import (
"fmt"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
"github.com/johnkerl/pgpg/go/lib/pkg/tokens"
)
// PGPG grammar node types (camelCase from grammar hints)
const (
NodeTypeStatementBlock = "StatementBlock"
NodeTypeStatementBlockInBraces = "StatementBlockInBraces"
NodeTypeAssignment = "Assignment"
NodeTypeCompoundAssignment = "CompoundAssignment"
NodeTypeUnset = "Unset"
NodeTypeBareBoolean = "BareBoolean"
NodeTypeFilterStatement = "FilterStatement"
NodeTypeRedirectWrite = "RedirectWrite"
NodeTypeRedirectAppend = "RedirectAppend"
NodeTypeRedirectPipe = "RedirectPipe"
NodeTypeRedirectTargetStdout = "RedirectTargetStdout"
NodeTypeRedirectTargetStderr = "RedirectTargetStderr"
NodeTypeRedirectTargetRvalue = "RedirectTargetRvalue"
NodeTypePrintStatement = "PrintStatement"
NodeTypePrintnStatement = "PrintnStatement"
NodeTypeEprintStatement = "EprintStatement"
NodeTypeEprintnStatement = "EprintnStatement"
NodeTypeDumpStatement = "DumpStatement"
NodeTypeEdumpStatement = "EdumpStatement"
NodeTypeTeeStatement = "TeeStatement"
NodeTypeEmit1Statement = "Emit1Statement"
NodeTypeEmitStatement = "EmitStatement"
NodeTypeEmitPStatement = "EmitPStatement"
NodeTypeEmitFStatement = "EmitFStatement"
NodeTypeBeginBlock = "BeginBlock"
NodeTypeEndBlock = "EndBlock"
NodeTypeCondBlock = "CondBlock"
NodeTypeIfChain = "IfChain"
NodeTypeIfItem = "IfItem"
NodeTypeWhileLoop = "WhileLoop"
NodeTypeDoWhileLoop = "DoWhileLoop"
NodeTypeForLoopOneVariable = "ForLoopOneVariable"
NodeTypeForLoopTwoVariable = "ForLoopTwoVariable"
NodeTypeForLoopMultivariable = "ForLoopMultivariable"
NodeTypeTripleForLoop = "TripleForLoop"
NodeTypeBreakStatement = "BreakStatement"
NodeTypeContinueStatement = "ContinueStatement"
NodeTypeReturnStatement = "ReturnStatement"
NodeTypeSubroutineCallsite = "SubroutineCallsite"
NodeTypeNamedFunctionDefinition = "NamedFunctionDefinition"
NodeTypeSubroutineDefinition = "SubroutineDefinition"
NodeTypeUnnamedFunctionDefinition = "UnnamedFunctionDefinition"
NodeTypeParameterList = "ParameterList"
NodeTypeParameter = "Parameter"
NodeTypeDirectFieldValue = "DirectFieldValue"
NodeTypeIndirectFieldValue = "IndirectFieldValue"
NodeTypeBracedFieldValue = "BracedFieldValue"
NodeTypeFullSrec = "FullSrec"
NodeTypeDirectOosvarValue = "DirectOosvarValue"
NodeTypeIndirectOosvarValue = "IndirectOosvarValue"
NodeTypeBracedOosvarValue = "BracedOosvarValue"
NodeTypeFullOosvar = "FullOosvar"
NodeTypeLocalVariable = "LocalVariable"
NodeTypeOperator = "Operator"
NodeTypeDotOperator = "DotOperator"
NodeTypeFunctionCallsite = "FunctionCallsite"
NodeTypeArrayLiteral = "ArrayLiteral"
NodeTypeMapLiteral = "MapLiteral"
NodeTypeMapLiteralKeyValuePair = "MapLiteralKeyValuePair"
NodeTypeArrayOrMapIndexAccess = "ArrayOrMapIndexAccess"
NodeTypeArraySliceLoHi = "ArraySliceLoHi"
NodeTypeArraySliceHiOnly = "ArraySliceHiOnly"
NodeTypeArraySliceLoOnly = "ArraySliceLoOnly"
NodeTypeArraySliceFull = "ArraySliceFull"
NodeTypeParenthesized = "Parenthesized"
NodeTypeFcnArgs = "FcnArgs"
NodeTypeIntLiteral = "int_literal"
NodeTypeFloatLiteral = "float_literal"
NodeTypeStringLiteral = "string_literal"
NodeTypeBoolLiteral = "bool_literal"
NodeTypeNullLiteral = "null_literal"
NodeTypeMultiIndex = "MultiIndex"
NodeTypeTypedecl = "Typedecl"
// Synthetic nodes for $[[n]] / $[[[n]]] (positional indexing - not in PGPG grammar)
NodeTypePositionalFieldName = "PositionalFieldName"
NodeTypePositionalFieldValue = "PositionalFieldValue"
// For array slice empty bounds: [lo:], [:hi], [:]
NodeTypeArraySliceEmptyLowerIndex = "ArraySliceEmptyLowerIndex"
NodeTypeArraySliceEmptyUpperIndex = "ArraySliceEmptyUpperIndex"
)
// Injected during regexProtectPrePass (not from grammar)
const NodeTypeRegex = "Regex"
// NoOp: used when optional redirect/expressions are absent (PGPG may not produce these)
const NodeTypeNoOp = "NoOp"
// NodeTypePanic: special token for testing short-circuit (not in grammar)
const NodeTypePanic = "Panic"
// Types not in mlr.bnf (positional indexing, ENV, context vars excluded).
// Added so CST compiles; extend grammar to support.
const (
NodeTypeArraySliceAccess = "ArraySliceAccess" // alias for any of ArraySliceLoHi etc
NodeTypeArrayOrMapPositionalNameAccess = "ArrayOrMapPositionalNameAccess"
NodeTypeArrayOrMapPositionalValueAccess = "ArrayOrMapPositionalValueAccess"
NodeTypeEmitKeys = "EmitKeys"
NodeTypeEnvironmentVariable = "EnvironmentVariable"
NodeTypeRegexCaseInsensitive = "RegexCaseInsensitive"
NodeTypeContextVariable = "ContextVariable"
NodeTypeConstant = "Constant"
)
// tokenLit returns the lexeme text for an AST node's token, or "" if nil.
func tokenLit(node *asts.ASTNode) string {
if node == nil || node.Token == nil {
return ""
}
return node.Token.LexemeText()
}
// tokenLitStripDollarOrAt returns the lexeme with leading $ or @ stripped.
// Used for DirectFieldValue ($n -> n) and DirectOosvarValue (@x -> x).
func tokenLitStripDollarOrAt(node *asts.ASTNode) string {
s := tokenLit(node)
if len(s) >= 1 && (s[0] == '$' || s[0] == '@') {
return s[1:]
}
return s
}
// tokenLitStripBraced returns the lexeme with ${ / @{ and } stripped.
// Used for BracedFieldValue (${foo} -> foo) and BracedOosvarValue (@{bar} -> bar).
func tokenLitStripBraced(node *asts.ASTNode) string {
s := tokenLit(node)
if len(s) >= 4 && (s[0] == '$' || s[0] == '@') && s[1] == '{' && s[len(s)-1] == '}' {
return s[2 : len(s)-1]
}
return s
}
// pgpgTokenToLocationInfo formats location info for error messages.
func pgpgTokenToLocationInfo(tok *tokens.Token) string {
if tok == nil {
return ""
}
return fmt.Sprintf(" at DSL expression line %d column %d", tok.Location.LineNumber, tok.Location.ColumnNumber)
}

View file

@ -6,16 +6,17 @@ package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
type BreakNode struct {
}
func (root *RootNode) BuildBreakNode(astNode *dsl.ASTNode) (*BreakNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeBreak)
func (root *RootNode) BuildBreakNode(astNode *asts.ASTNode) (*BreakNode, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeBreakStatement))
lib.InternalCodingErrorIf(astNode.Children == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 0)
@ -32,8 +33,8 @@ func (node *BreakNode) Execute(state *runtime.State) (*BlockExitPayload, error)
type ContinueNode struct {
}
func (root *RootNode) BuildContinueNode(astNode *dsl.ASTNode) (*ContinueNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeContinue)
func (root *RootNode) BuildContinueNode(astNode *asts.ASTNode) (*ContinueNode, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeContinueStatement))
lib.InternalCodingErrorIf(astNode.Children == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 0)
@ -51,8 +52,8 @@ type ReturnNode struct {
returnValueExpression IEvaluable
}
func (root *RootNode) BuildReturnNode(astNode *dsl.ASTNode) (*ReturnNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeReturn)
func (root *RootNode) BuildReturnNode(astNode *asts.ASTNode) (*ReturnNode, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeReturnStatement))
lib.InternalCodingErrorIf(astNode.Children == nil)
if len(astNode.Children) == 0 {
return &ReturnNode{returnValueExpression: nil}, nil

View file

@ -4,9 +4,10 @@
package cst
import (
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
func NewStatementBlockNode() *StatementBlockNode {
@ -20,12 +21,12 @@ func (node *StatementBlockNode) AppendStatementNode(executable IExecutable) {
}
func (root *RootNode) BuildStatementBlockNodeFromBeginOrEnd(
astBeginOrEndNode *dsl.ASTNode,
astBeginOrEndNode *asts.ASTNode,
) (*StatementBlockNode, error) {
lib.InternalCodingErrorIf(
astBeginOrEndNode.Type != dsl.NodeTypeBeginBlock &&
astBeginOrEndNode.Type != dsl.NodeTypeEndBlock,
astBeginOrEndNode.Type != asts.NodeType(NodeTypeBeginBlock) &&
astBeginOrEndNode.Type != asts.NodeType(NodeTypeEndBlock),
)
lib.InternalCodingErrorIf(astBeginOrEndNode.Children == nil)
// TODO: change the BNF to make it always 1 in the AST
@ -58,7 +59,13 @@ func (root *RootNode) BuildStatementBlockNodeFromBeginOrEnd(
// * IntLiteral "4"
astStatementBlockNode := astBeginOrEndNode.Children[0]
lib.InternalCodingErrorIf(astStatementBlockNode.Type != dsl.NodeTypeStatementBlock)
// PGPG: BeginBlock/EndBlock have StatementBlockInBraces as child.
// With "parent":1,"children":[1], StatementBlockInBraces.Children[0] is the StatementBlock.
// Unwrap so we pass StatementBlock to BuildStatementBlockNode.
if astStatementBlockNode.Type == asts.NodeType(NodeTypeStatementBlockInBraces) {
lib.InternalCodingErrorIf(astStatementBlockNode.Children == nil || len(astStatementBlockNode.Children) < 1)
astStatementBlockNode = astStatementBlockNode.Children[0]
}
statementBlockNode, err := root.BuildStatementBlockNode(astStatementBlockNode)
if err != nil {
return nil, err
@ -67,10 +74,15 @@ func (root *RootNode) BuildStatementBlockNodeFromBeginOrEnd(
}
func (root *RootNode) BuildStatementBlockNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (*StatementBlockNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeStatementBlock)
// PGPG: StatementBlockInBraces has "children":[1] so its child is StatementBlock; unwrap.
if astNode.Type == asts.NodeType(NodeTypeStatementBlockInBraces) &&
astNode.Children != nil && len(astNode.Children) == 1 &&
astNode.Children[0].Type == asts.NodeType(NodeTypeStatementBlock) {
astNode = astNode.Children[0]
}
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeStatementBlock))
statementBlockNode := NewStatementBlockNode()

View file

@ -7,10 +7,10 @@ import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/bifs"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// BIF_next is for mlr script: reads next record from input stream.
@ -31,16 +31,21 @@ func BIF_next(state *runtime.State) *mlrval.Mlrval {
}
func (root *RootNode) BuildBuiltinFunctionCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(
astNode.Type != dsl.NodeTypeFunctionCallsite &&
astNode.Type != dsl.NodeTypeOperator,
astNode.Type != asts.NodeType(NodeTypeFunctionCallsite) &&
astNode.Type != asts.NodeType(NodeTypeOperator),
)
lib.InternalCodingErrorIf(astNode.Token == nil)
lib.InternalCodingErrorIf(astNode.Children == nil)
functionName := string(astNode.Token.Lit)
functionName := tokenLit(astNode)
// PGPG produces ternary with "?" token; builtin is registered as "?:"
if functionName == "?" && len(astNode.Children) == 3 {
functionName = "?:"
}
builtinFunctionInfo := BuiltinFunctionManagerInstance.LookUp(functionName)
if builtinFunctionInfo != nil {
@ -79,7 +84,7 @@ func (root *RootNode) BuildBuiltinFunctionCallsiteNode(
}
func (root *RootNode) BuildMultipleArityFunctionCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
callsiteArity := len(astNode.Children)
@ -104,7 +109,7 @@ type ZaryFunctionCallsiteNode struct {
}
func BuildZaryFunctionCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
callsiteArity := len(astNode.Children)
@ -125,7 +130,7 @@ func BuildZaryFunctionCallsiteNode(
}
func BuildZaryFunctionWithStateCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
callsiteArity := len(astNode.Children)
@ -167,7 +172,7 @@ type UnaryFunctionCallsiteNode struct {
}
func (root *RootNode) BuildUnaryFunctionCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
callsiteArity := len(astNode.Children)
@ -205,7 +210,7 @@ type UnaryFunctionWithContextCallsiteNode struct {
}
func (root *RootNode) BuildUnaryFunctionWithContextCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
callsiteArity := len(astNode.Children)
@ -244,7 +249,7 @@ type BinaryFunctionCallsiteNode struct {
}
func (root *RootNode) BuildBinaryFunctionCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
callsiteArity := len(astNode.Children)
@ -301,6 +306,56 @@ func (root *RootNode) BuildBinaryFunctionCallsiteNode(
}, nil
}
// buildBinaryOperatorFromEvaluables builds an IEvaluable for (a baseOp b) from
// evaluables, without going through the AST. Used by compound assignment (e.g.
// @sum += $x -> @sum = @sum + $x). rhsASTNode is used for the dot operator's
// string2 (map key) when baseOp is ".".
func (root *RootNode) buildBinaryOperatorFromEvaluables(
baseOp string,
evaluable1, evaluable2 IEvaluable,
rhsASTNode *asts.ASTNode,
) (IEvaluable, error) {
if baseOp == "." {
return &DotCallsiteNode{
evaluable1: evaluable1,
evaluable2: evaluable2,
string2: tokenLit(rhsASTNode),
}, nil
}
builtinFunctionInfo := BuiltinFunctionManagerInstance.LookUp(baseOp)
if builtinFunctionInfo == nil {
return nil, fmt.Errorf("compound assignment: unknown operator %q", baseOp)
}
if builtinFunctionInfo.binaryFunc != nil {
if baseOp == "&&" {
return BuildLogicalANDOperatorNode(evaluable1, evaluable2), nil
}
if baseOp == "||" {
return BuildLogicalOROperatorNode(evaluable1, evaluable2), nil
}
if baseOp == "??" {
return BuildAbsentCoalesceOperatorNode(evaluable1, evaluable2), nil
}
if baseOp == "???" {
return BuildEmptyCoalesceOperatorNode(evaluable1, evaluable2), nil
}
return &BinaryFunctionCallsiteNode{
binaryFunc: builtinFunctionInfo.binaryFunc,
evaluable1: evaluable1,
evaluable2: evaluable2,
}, nil
}
if builtinFunctionInfo.binaryFuncWithState != nil {
return &BinaryFunctionWithStateCallsiteNode{
binaryFuncWithState: builtinFunctionInfo.binaryFuncWithState,
evaluable1: evaluable1,
evaluable2: evaluable2,
}, nil
}
return nil, fmt.Errorf("compound assignment: operator %q not implemented", baseOp)
}
func (node *BinaryFunctionCallsiteNode) Evaluate(
state *runtime.State,
) *mlrval.Mlrval {
@ -317,7 +372,7 @@ type BinaryFunctionWithStateCallsiteNode struct {
}
func (root *RootNode) BuildBinaryFunctionWithStateCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
callsiteArity := len(astNode.Children)
@ -366,7 +421,7 @@ type TernaryFunctionWithStateCallsiteNode struct {
}
func (root *RootNode) BuildTernaryFunctionWithStateCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
callsiteArity := len(astNode.Children)
@ -451,7 +506,7 @@ type RegexCaptureBinaryFunctionCallsiteNode struct {
}
func (root *RootNode) BuildRegexCaptureBinaryFunctionCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
callsiteArity := len(astNode.Children)
@ -503,7 +558,7 @@ type DotCallsiteNode struct {
}
func (root *RootNode) BuildDotCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
callsiteArity := len(astNode.Children)
expectedArity := 2
@ -529,7 +584,7 @@ func (root *RootNode) BuildDotCallsiteNode(
return &DotCallsiteNode{
evaluable1: evaluable1,
evaluable2: evaluable2,
string2: string(astNode.Children[1].Token.Lit),
string2: tokenLit(astNode.Children[1]),
}, nil
}
@ -562,7 +617,7 @@ type TernaryFunctionCallsiteNode struct {
}
func (root *RootNode) BuildTernaryFunctionCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
callsiteArity := len(astNode.Children)
@ -623,7 +678,7 @@ type VariadicFunctionCallsiteNode struct {
}
func (root *RootNode) BuildVariadicFunctionCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Children == nil)
@ -680,7 +735,7 @@ type VariadicFunctionWithStateCallsiteNode struct {
}
func (root *RootNode) BuildVariadicFunctionWithStateCallsiteNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
builtinFunctionInfo *BuiltinFunctionInfo,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Children == nil)

View file

@ -7,10 +7,10 @@ import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/bifs"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
type ArrayLiteralNode struct {
@ -18,9 +18,9 @@ type ArrayLiteralNode struct {
}
func (node *RootNode) BuildArrayLiteralNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeArrayLiteral)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeArrayLiteral))
// An empty array should have non-nil zero-length children, not nil
// children
lib.InternalCodingErrorIf(astNode.Children == nil)
@ -56,9 +56,9 @@ type ArrayOrMapIndexAccessNode struct {
}
func (node *RootNode) BuildArrayOrMapIndexAccessNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeArrayOrMapIndexAccess)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeArrayOrMapIndexAccess))
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
baseASTNode := astNode.Children[0]
@ -198,30 +198,81 @@ type ArraySliceAccessNode struct {
}
func (node *RootNode) BuildArraySliceAccessNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeArraySliceAccess)
lib.InternalCodingErrorIf(len(astNode.Children) != 3)
baseASTNode := astNode.Children[0]
lowerIndexASTNode := astNode.Children[1]
upperIndexASTNode := astNode.Children[2]
// PGPG produces ArraySliceLoHi (3 children), ArraySliceHiOnly (2), ArraySliceLoOnly (2), ArraySliceFull (1)
switch astNode.Type {
case asts.NodeType(NodeTypeArraySliceLoHi):
// [lo:hi]
lib.InternalCodingErrorIf(len(astNode.Children) != 3)
return node.buildArraySliceAccessNodeFromChildren(astNode.Children[0], astNode.Children[1], astNode.Children[2])
case asts.NodeType(NodeTypeArraySliceHiOnly):
// [:hi] - lower defaults to 1
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
synthLower, _ := node.BuildArraySliceEmptyLowerIndexNode(asts.NewASTNode(nil, asts.NodeType(NodeTypeArraySliceEmptyLowerIndex), nil))
baseEval, err := node.BuildEvaluableNode(astNode.Children[0])
if err != nil {
return nil, err
}
upperEval, err := node.BuildEvaluableNode(astNode.Children[1])
if err != nil {
return nil, err
}
return node.buildArraySliceAccessFromEvaluables(baseEval, synthLower, upperEval)
case asts.NodeType(NodeTypeArraySliceLoOnly):
// [lo:] - upper defaults to n
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
synthUpper, _ := node.BuildArraySliceEmptyUpperIndexNode(asts.NewASTNode(nil, asts.NodeType(NodeTypeArraySliceEmptyUpperIndex), nil))
baseEval, err := node.BuildEvaluableNode(astNode.Children[0])
if err != nil {
return nil, err
}
lowerEval, err := node.BuildEvaluableNode(astNode.Children[1])
if err != nil {
return nil, err
}
return node.buildArraySliceAccessFromEvaluables(baseEval, lowerEval, synthUpper)
case asts.NodeType(NodeTypeArraySliceFull):
// [:]
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
synthLower, _ := node.BuildArraySliceEmptyLowerIndexNode(asts.NewASTNode(nil, asts.NodeType(NodeTypeArraySliceEmptyLowerIndex), nil))
synthUpper, _ := node.BuildArraySliceEmptyUpperIndexNode(asts.NewASTNode(nil, asts.NodeType(NodeTypeArraySliceEmptyUpperIndex), nil))
baseEval, err := node.BuildEvaluableNode(astNode.Children[0])
if err != nil {
return nil, err
}
return node.buildArraySliceAccessFromEvaluables(baseEval, synthLower, synthUpper)
default:
lib.InternalCodingErrorIf(true)
return nil, nil
}
}
func (node *RootNode) buildArraySliceAccessNodeFromChildren(
baseASTNode *asts.ASTNode,
lowerIndexASTNode *asts.ASTNode,
upperIndexASTNode *asts.ASTNode,
) (IEvaluable, error) {
baseEvaluable, err := node.BuildEvaluableNode(baseASTNode)
if err != nil {
return nil, err
}
lowerIndexEvaluable, err := node.BuildEvaluableNode(lowerIndexASTNode)
if err != nil {
return nil, err
}
upperIndexEvaluable, err := node.BuildEvaluableNode(upperIndexASTNode)
if err != nil {
return nil, err
}
return node.buildArraySliceAccessFromEvaluables(baseEvaluable, lowerIndexEvaluable, upperIndexEvaluable)
}
func (node *RootNode) buildArraySliceAccessFromEvaluables(
baseEvaluable IEvaluable,
lowerIndexEvaluable IEvaluable,
upperIndexEvaluable IEvaluable,
) (IEvaluable, error) {
return &ArraySliceAccessNode{
baseEvaluable: baseEvaluable,
lowerIndexEvaluable: lowerIndexEvaluable,
@ -287,9 +338,9 @@ type PositionalFieldNameNode struct {
}
func (node *RootNode) BuildPositionalFieldNameNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypePositionalFieldName)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypePositionalFieldName))
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
indexASTNode := astNode.Children[0]
@ -333,9 +384,9 @@ type PositionalFieldValueNode struct {
}
func (node *RootNode) BuildPositionalFieldValueNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypePositionalFieldValue)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypePositionalFieldValue))
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
indexASTNode := astNode.Children[0]
@ -379,9 +430,9 @@ type ArrayOrMapPositionalNameAccessNode struct {
}
func (node *RootNode) BuildArrayOrMapPositionalNameAccessNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeArrayOrMapPositionalNameAccess)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeArrayOrMapPositionalNameAccess))
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
baseASTNode := astNode.Children[0]
@ -455,9 +506,9 @@ type ArrayOrMapPositionalValueAccessNode struct {
}
func (node *RootNode) BuildArrayOrMapPositionalValueAccessNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeArrayOrMapPositionalValueAccess)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeArrayOrMapPositionalValueAccess))
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
baseASTNode := astNode.Children[0]
@ -546,9 +597,9 @@ type MapLiteralNode struct {
}
func (node *RootNode) BuildMapLiteralNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeMapLiteral)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeMapLiteral))
// An empty array should have non-nil zero-length children, not nil
// children
lib.InternalCodingErrorIf(astNode.Children == nil)

View file

@ -6,23 +6,24 @@ package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/parsing/token"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
"github.com/johnkerl/pgpg/go/lib/pkg/tokens"
)
type CondBlockNode struct {
conditionNode IEvaluable
conditionToken *token.Token
conditionToken *tokens.Token
statementBlockNode *StatementBlockNode
}
// Sample AST:
func (root *RootNode) BuildCondBlockNode(astNode *dsl.ASTNode) (*CondBlockNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeCondBlock)
func (root *RootNode) BuildCondBlockNode(astNode *asts.ASTNode) (*CondBlockNode, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeCondBlock))
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
conditionNode, err := root.BuildEvaluableNode(astNode.Children[0])
@ -58,7 +59,7 @@ func (node *CondBlockNode) Execute(
} else if !isBool {
return nil, fmt.Errorf(
"conditional expression did not evaluate to boolean%s",
dsl.TokenToLocationInfo(node.conditionToken),
pgpgTokenToLocationInfo(node.conditionToken),
)
}

View file

@ -19,11 +19,11 @@ import (
"os"
"strings"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/output"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/miller/v6/pkg/types"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
type tDumpToRedirectFunc func(
@ -38,16 +38,16 @@ type DumpStatementNode struct {
outputHandlerManager output.OutputHandlerManager // for file/pipe targets
}
func (root *RootNode) BuildDumpStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeDumpStatement)
func (root *RootNode) BuildDumpStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeDumpStatement))
return root.buildDumpxStatementNode(
astNode,
os.Stdout,
)
}
func (root *RootNode) BuildEdumpStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEdumpStatement)
func (root *RootNode) BuildEdumpStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeEdumpStatement))
return root.buildDumpxStatementNode(
astNode,
os.Stderr,
@ -57,24 +57,55 @@ func (root *RootNode) BuildEdumpStatementNode(astNode *dsl.ASTNode) (IExecutable
// Common code for building dump/edump nodes
func (root *RootNode) buildDumpxStatementNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
defaultOutputStream *os.File,
) (IExecutable, error) {
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
expressionsNode := astNode.Children[0]
redirectorNode := astNode.Children[1]
// Normalize PGPG AST to 2-child layout (expressions, redirector).
// PGPG produces: 0 children (dump), 1 child (dump s or dump > file), 2+ children (dump s,t or dump > file, s).
// With "dump Redirector comma FcnArgs", children are [Redirector, FcnArgs].
// With "dump FcnArgs" (with_adopted_grandchildren), children are the Rvalues directly.
var expressionsNode, redirectorNode *asts.ASTNode
isRedirector := func(n *asts.ASTNode) bool {
return n.Type == asts.NodeType(NodeTypeRedirectWrite) ||
n.Type == asts.NodeType(NodeTypeRedirectAppend) ||
n.Type == asts.NodeType(NodeTypeRedirectPipe)
}
switch len(astNode.Children) {
case 0:
expressionsNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
case 1:
if isRedirector(astNode.Children[0]) {
expressionsNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
redirectorNode = astNode.Children[0]
} else {
expressionsNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), astNode.Children)
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
}
default:
if isRedirector(astNode.Children[0]) {
// dump Redirector comma FcnArgs -> [Redirector, FcnArgs]
expressionsNode = astNode.Children[1]
redirectorNode = astNode.Children[0]
} else {
// dump FcnArgs with_adopted_grandchildren -> [Rvalue, Rvalue, ...]
expressionsNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), astNode.Children)
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Things to be dumped, e.g. $a and $b in 'dump > "foo.dat", $a, $b'.
var expressionEvaluables []IEvaluable = nil
if expressionsNode.Type == dsl.NodeTypeNoOp {
if expressionsNode.Type == asts.NodeType(NodeTypeNoOp) {
// Just 'dump' without 'dump $something'
expressionEvaluables = make([]IEvaluable, 1)
expressionEvaluable := root.BuildFullOosvarRvalueNode()
expressionEvaluables[0] = expressionEvaluable
} else if expressionsNode.Type == dsl.NodeTypeFunctionCallsite {
} else if expressionsNode.Type == asts.NodeType(NodeTypeFunctionCallsite) ||
expressionsNode.Type == asts.NodeType(NodeTypeFcnArgs) {
expressionEvaluables = make([]IEvaluable, len(expressionsNode.Children))
for i, childNode := range expressionsNode.Children {
expressionEvaluable, err := root.BuildEvaluableNode(childNode)
@ -97,7 +128,7 @@ func (root *RootNode) buildDumpxStatementNode(
outputHandlerManager: nil,
}
if redirectorNode.Type == dsl.NodeTypeNoOp {
if redirectorNode.Type == asts.NodeType(NodeTypeNoOp) {
// No > >> or | was provided.
if defaultOutputStream == os.Stdout {
retval.dumpToRedirectFunc = retval.dumpToStdout
@ -113,23 +144,27 @@ func (root *RootNode) buildDumpxStatementNode(
redirectorTargetNode := redirectorNode.Children[0]
var err error
if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStdout {
if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetStdout) {
retval.dumpToRedirectFunc = retval.dumpToStdout
} else if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStderr {
} else if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetStderr) {
retval.dumpToRedirectFunc = retval.dumpToStderr
} else {
retval.dumpToRedirectFunc = retval.dumpToFileOrPipe
retval.redirectorTargetEvaluable, err = root.BuildEvaluableNode(redirectorTargetNode)
targetNode := redirectorTargetNode
if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetRvalue) &&
redirectorTargetNode.Children != nil && len(redirectorTargetNode.Children) > 0 {
targetNode = redirectorTargetNode.Children[0]
}
retval.redirectorTargetEvaluable, err = root.BuildEvaluableNode(targetNode)
if err != nil {
return nil, err
}
if redirectorNode.Type == dsl.NodeTypeRedirectWrite {
if redirectorNode.Type == asts.NodeType(NodeTypeRedirectWrite) {
retval.outputHandlerManager = output.NewFileWritetHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectAppend {
} else if redirectorNode.Type == asts.NodeType(NodeTypeRedirectAppend) {
retval.outputHandlerManager = output.NewFileAppendHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectPipe {
} else if redirectorNode.Type == asts.NodeType(NodeTypeRedirectPipe) {
retval.outputHandlerManager = output.NewPipeWriteHandlerManager(root.recordWriterOptions)
} else {
return nil, fmt.Errorf("unhandled redirector node type %s", string(redirectorNode.Type))

View file

@ -20,23 +20,23 @@ package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/miller/v6/pkg/types"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
type Emit1StatementNode struct {
evaluable IEvaluable
}
func (root *RootNode) BuildEmit1StatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmit1Statement)
func (root *RootNode) BuildEmit1StatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeEmit1Statement))
return root.buildEmit1StatementNode(astNode, false)
}
func (root *RootNode) buildEmit1StatementNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
isEmitP bool,
) (IExecutable, error) {
lib.InternalCodingErrorIf(len(astNode.Children) != 1)

View file

@ -40,12 +40,12 @@ import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/cli"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/output"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/miller/v6/pkg/types"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// Shared by emit and emitp
@ -94,40 +94,184 @@ type EmitXStatementNode struct {
flatsep string
}
func (root *RootNode) BuildEmitStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmitStatement)
func (root *RootNode) BuildEmitStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeEmitStatement))
return root.buildEmitXStatementNode(astNode, false)
}
func (root *RootNode) BuildEmitPStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmitPStatement)
func (root *RootNode) BuildEmitPStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeEmitPStatement))
return root.buildEmitXStatementNode(astNode, true)
}
var EMITX_NAMED_NODE_TYPES = map[dsl.TNodeType]bool{
dsl.NodeTypeLocalVariable: true,
dsl.NodeTypeDirectOosvarValue: true,
dsl.NodeTypeIndirectOosvarValue: true,
dsl.NodeTypeDirectFieldValue: true,
dsl.NodeTypeIndirectFieldValue: true,
func allChildrenAreNamedNodes(children []*asts.ASTNode) bool {
for _, c := range children {
if !EMITX_NAMED_NODE_TYPES[c.Type] && !EMITX_NAMELESS_NODE_TYPES[c.Type] {
return false
}
}
return len(children) > 0
}
var EMITX_NAMELESS_NODE_TYPES = map[dsl.TNodeType]bool{
dsl.NodeTypeFullSrec: true,
dsl.NodeTypeFullOosvar: true,
dsl.NodeTypeMapLiteral: true,
var EMITX_NAMED_NODE_TYPES = map[asts.NodeType]bool{
asts.NodeType(NodeTypeLocalVariable): true,
asts.NodeType(NodeTypeDirectOosvarValue): true,
asts.NodeType(NodeTypeIndirectOosvarValue): true,
asts.NodeType(NodeTypeBracedOosvarValue): true, // @{variable.name}
asts.NodeType(NodeTypeDirectFieldValue): true,
asts.NodeType(NodeTypeIndirectFieldValue): true,
asts.NodeType(NodeTypeArrayOrMapIndexAccess): true, // $x[1], @a[111], etc.
asts.NodeType(NodeTypeDotOperator): true, // $a.$b (string concat or map access)
asts.NodeType(NodeTypeFunctionCallsite): true,
}
var EMITX_NAMELESS_NODE_TYPES = map[asts.NodeType]bool{
asts.NodeType(NodeTypeFullSrec): true,
asts.NodeType(NodeTypeFullOosvar): true,
asts.NodeType(NodeTypeMapLiteral): true,
}
// emitKeyName extracts the key name for emit/emitp output. Strips leading $ or @
// and for braced forms strips ${ } or @{ } so that @sum emits as "sum", ${x+y} as "x+y".
func emitKeyName(childNode *asts.ASTNode) string {
// Walk to base for ArrayOrMapIndexAccess/DotOperator (e.g. @v[1][1] -> "v")
walker := childNode
for walker != nil &&
(walker.Type == asts.NodeType(NodeTypeArrayOrMapIndexAccess) ||
walker.Type == asts.NodeType(NodeTypeDotOperator)) &&
walker.Children != nil && len(walker.Children) > 0 {
walker = walker.Children[0]
}
if walker != nil {
childNode = walker
}
var s string
if childNode.Type == asts.NodeType(NodeTypeBracedFieldValue) ||
childNode.Type == asts.NodeType(NodeTypeBracedOosvarValue) {
s = tokenLitStripBraced(childNode)
} else {
s = tokenLitStripDollarOrAt(childNode)
}
if s == "" && childNode.Children != nil && len(childNode.Children) > 0 {
s = tokenLitStripDollarOrAt(childNode.Children[0])
}
if s == "" {
s = tokenLit(childNode)
}
if len(s) >= 1 && (s[0] == '$' || s[0] == '@') {
return s[1:]
}
return s
}
// EMIT AND EMITP
func (root *RootNode) buildEmitXStatementNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
isEmitP bool,
) (IExecutable, error) {
lib.InternalCodingErrorIf(len(astNode.Children) != 3)
emittablesNode := astNode.Children[0]
keysNode := astNode.Children[1]
redirectorNode := astNode.Children[2]
// Normalize PGPG AST to 3-child layout: emittables, keys, redirector.
// PGPG produces: 1 child (emitp @s), 2 children (emitp > file, @s OR emitp @s, "key"),
// or 3+ adopted children (emitp @s, "k1", "k2").
var emittablesNode, keysNode, redirectorNode *asts.ASTNode
isRedirector := func(n *asts.ASTNode) bool {
return n.Type == asts.NodeType(NodeTypeRedirectWrite) ||
n.Type == asts.NodeType(NodeTypeRedirectAppend) ||
n.Type == asts.NodeType(NodeTypeRedirectPipe)
}
switch len(astNode.Children) {
case 1:
child := astNode.Children[0]
if child.Type == asts.NodeType(NodeTypeFcnArgs) && child.Children != nil && len(child.Children) >= 1 {
// PGPG gave us FcnArgs directly: [emittable], [emittable, emittable, ...] (lashed), or
// [emittable, key1, key2, ...]
if len(child.Children) == 1 {
emittablesNode = child
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
} else if len(child.Children) >= 2 &&
(EMITX_NAMED_NODE_TYPES[child.Children[0].Type] || EMITX_NAMELESS_NODE_TYPES[child.Children[0].Type]) &&
!EMITX_NAMED_NODE_TYPES[child.Children[1].Type] {
// First is emittable, rest are index keys
emittablesNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), []*asts.ASTNode{child.Children[0]})
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeEmitKeys), child.Children[1:])
} else {
emittablesNode = child
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
}
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
} else {
// PGPG: kw_emitp FcnArgs with with_adopted_grandchildren -> single child is emittable
emittablesNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), []*asts.ASTNode{child})
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
}
case 2:
if isRedirector(astNode.Children[0]) {
// PGPG: kw_emit Redirector comma FcnArgs -> children: [Redirector, FcnArgs]
// FcnArgs may be [emittable], [emittable, emittable, ...] (lashed), or [emittable, key1, key2, ...]
fcnArgs := astNode.Children[1]
if fcnArgs.Type == asts.NodeType(NodeTypeFcnArgs) && fcnArgs.Children != nil && len(fcnArgs.Children) >= 2 &&
(EMITX_NAMED_NODE_TYPES[fcnArgs.Children[0].Type] || EMITX_NAMELESS_NODE_TYPES[fcnArgs.Children[0].Type]) &&
!EMITX_NAMED_NODE_TYPES[fcnArgs.Children[1].Type] {
// First is emittable, rest are index keys (string literals)
emittablesNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), []*asts.ASTNode{fcnArgs.Children[0]})
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeEmitKeys), fcnArgs.Children[1:])
} else {
emittablesNode = fcnArgs
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
}
redirectorNode = astNode.Children[0]
} else if astNode.Children[0].Type == asts.NodeType(NodeTypeFcnArgs) &&
astNode.Children[0].Children != nil && len(astNode.Children[0].Children) >= 2 &&
astNode.Children[1].Type == asts.NodeType(NodeTypeFcnArgs) {
// PGPG: kw_emitp FcnArgsParen comma FcnArgs -> [emittables, keys]
emittablesNode = astNode.Children[0]
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeEmitKeys), astNode.Children[1].Children)
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
} else if EMITX_NAMED_NODE_TYPES[astNode.Children[0].Type] && EMITX_NAMED_NODE_TYPES[astNode.Children[1].Type] {
// PGPG: kw_emitp FcnArgsParen -> lashed emitp (@a, @b), both are emittables
emittablesNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), astNode.Children)
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
} else {
// PGPG: kw_emitp FcnArgs with adoption -> [emittable, key]; first is emittable, rest are keys
emittablesNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), []*asts.ASTNode{astNode.Children[0]})
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeEmitKeys), []*asts.ASTNode{astNode.Children[1]})
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
}
default:
if isRedirector(astNode.Children[0]) {
// len 3: emit Redirector comma FcnArgsParen comma FcnArgs -> [Redirector, emittables, keys]
// len 2: emit Redirector comma FcnArgs -> [Redirector, FcnArgs]; split if FcnArgs=[emittable, keys...]
if len(astNode.Children) >= 3 && astNode.Children[2].Type == asts.NodeType(NodeTypeFcnArgs) &&
astNode.Children[2].Children != nil {
emittablesNode = astNode.Children[1]
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeEmitKeys), astNode.Children[2].Children)
} else {
fcnArgs := astNode.Children[1]
if fcnArgs.Type == asts.NodeType(NodeTypeFcnArgs) && fcnArgs.Children != nil && len(fcnArgs.Children) >= 2 &&
(EMITX_NAMED_NODE_TYPES[fcnArgs.Children[0].Type] || EMITX_NAMELESS_NODE_TYPES[fcnArgs.Children[0].Type]) &&
!EMITX_NAMED_NODE_TYPES[fcnArgs.Children[1].Type] {
emittablesNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), []*asts.ASTNode{fcnArgs.Children[0]})
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeEmitKeys), fcnArgs.Children[1:])
} else {
emittablesNode = fcnArgs
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
}
}
redirectorNode = astNode.Children[0]
} else if allChildrenAreNamedNodes(astNode.Children) {
// PGPG: kw_emitp FcnArgsParen with 3+ args -> lashed emitp (@a, @b, @c)
emittablesNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), astNode.Children)
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
} else {
// emitp FcnArgs with adoption -> [emittable, key1, key2, ...]
emittablesNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), []*asts.ASTNode{astNode.Children[0]})
keysNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeEmitKeys), astNode.Children[1:])
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
}
}
retval := &EmitXStatementNode{
isEmitP: isEmitP,
@ -144,10 +288,18 @@ func (root *RootNode) buildEmitXStatementNode(
lib.InternalCodingErrorIf(len(emittablesNode.Children) < 1)
if len(emittablesNode.Children) == 1 {
childNode := emittablesNode.Children[0]
// Unwrap Parenthesized: emitp (@a) parses as Parenthesized containing @a
if childNode.Type == asts.NodeType(NodeTypeParenthesized) &&
childNode.Children != nil && len(childNode.Children) == 1 {
childNode = childNode.Children[0]
}
if EMITX_NAMED_NODE_TYPES[childNode.Type] {
if childNode.Type == asts.NodeType(NodeTypeLocalVariable) && tokenLit(childNode) == "all" {
// "emit all" / "emitp all" means emit all out-of-stream variables (same as @*)
retval.topLevelEvaluableMap = root.BuildFullOosvarRvalueNode()
} else if EMITX_NAMED_NODE_TYPES[childNode.Type] {
retval.topLevelNameList = make([]string, 1)
retval.topLevelNameList[0] = string(childNode.Token.Lit)
retval.topLevelNameList[0] = emitKeyName(childNode)
retval.topLevelEvaluableList = make([]IEvaluable, 1)
evaluable, err := root.BuildEvaluableNode(childNode)
@ -173,9 +325,9 @@ func (root *RootNode) buildEmitXStatementNode(
} else {
retval.isLashed = true
for _, childNode := range emittablesNode.Children {
if !EMITX_NAMED_NODE_TYPES[childNode.Type] {
if !EMITX_NAMED_NODE_TYPES[childNode.Type] && !EMITX_NAMELESS_NODE_TYPES[childNode.Type] {
return nil, fmt.Errorf(
"lashed-emit node types must be local variables, field names, or oosvars; got %s",
"lashed-emit node types must be local variables, field names, oosvars, or maps; got %s",
childNode.Type,
)
}
@ -184,7 +336,7 @@ func (root *RootNode) buildEmitXStatementNode(
retval.topLevelNameList = make([]string, len(emittablesNode.Children))
retval.topLevelEvaluableList = make([]IEvaluable, len(emittablesNode.Children))
for i, childNode := range emittablesNode.Children {
retval.topLevelNameList[i] = string(childNode.Token.Lit)
retval.topLevelNameList[i] = emitKeyName(childNode)
evaluable, err := root.BuildEvaluableNode(childNode)
if err != nil {
return nil, err
@ -197,8 +349,8 @@ func (root *RootNode) buildEmitXStatementNode(
// Indices (if any) on the emittables
isIndexed := false
if keysNode.Type != dsl.NodeTypeNoOp { // There are "x","y" present
lib.InternalCodingErrorIf(keysNode.Type != dsl.NodeTypeEmitKeys)
if keysNode.Type != asts.NodeType(NodeTypeNoOp) { // There are "x","y" present
lib.InternalCodingErrorIf(keysNode.Type != asts.NodeType(NodeTypeEmitKeys))
isIndexed = true
numKeys := len(keysNode.Children)
retval.indexEvaluables = make([]IEvaluable, numKeys)
@ -232,7 +384,7 @@ func (root *RootNode) buildEmitXStatementNode(
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Redirections and redirection targets (the thing after > >> |, if any).
if redirectorNode.Type == dsl.NodeTypeNoOp {
if redirectorNode.Type == asts.NodeType(NodeTypeNoOp) {
// No > >> or | was provided.
retval.emitToRedirectFunc = retval.emitRecordToRecordStream
} else {
@ -242,27 +394,31 @@ func (root *RootNode) buildEmitXStatementNode(
redirectorTargetNode := redirectorNode.Children[0]
var err error
if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStdout {
if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetStdout) {
retval.emitToRedirectFunc = retval.emitRecordToFileOrPipe
retval.outputHandlerManager = output.NewStdoutWriteHandlerManager(root.recordWriterOptions)
retval.redirectorTargetEvaluable = root.BuildStringLiteralNode("(stdout)")
} else if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStderr {
} else if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetStderr) {
retval.emitToRedirectFunc = retval.emitRecordToFileOrPipe
retval.outputHandlerManager = output.NewStderrWriteHandlerManager(root.recordWriterOptions)
retval.redirectorTargetEvaluable = root.BuildStringLiteralNode("(stderr)")
} else {
retval.emitToRedirectFunc = retval.emitRecordToFileOrPipe
retval.redirectorTargetEvaluable, err = root.BuildEvaluableNode(redirectorTargetNode)
targetNode := redirectorTargetNode
if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetRvalue) &&
redirectorTargetNode.Children != nil && len(redirectorTargetNode.Children) > 0 {
targetNode = redirectorTargetNode.Children[0]
}
retval.redirectorTargetEvaluable, err = root.BuildEvaluableNode(targetNode)
if err != nil {
return nil, err
}
if redirectorNode.Type == dsl.NodeTypeRedirectWrite {
if redirectorNode.Type == asts.NodeType(NodeTypeRedirectWrite) {
retval.outputHandlerManager = output.NewFileWritetHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectAppend {
} else if redirectorNode.Type == asts.NodeType(NodeTypeRedirectAppend) {
retval.outputHandlerManager = output.NewFileAppendHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectPipe {
} else if redirectorNode.Type == asts.NodeType(NodeTypeRedirectPipe) {
retval.outputHandlerManager = output.NewPipeWriteHandlerManager(root.recordWriterOptions)
} else {
return nil, fmt.Errorf("unhandled redirector node type %s", string(redirectorNode.Type))

View file

@ -6,12 +6,12 @@ package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/output"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/miller/v6/pkg/types"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// Examples:
@ -48,11 +48,27 @@ type EmitFStatementNode struct {
// * direct oosvar value "c"
// * no-op
func (root *RootNode) BuildEmitFStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmitFStatement)
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
expressionsNode := astNode.Children[0]
redirectorNode := astNode.Children[1]
func (root *RootNode) BuildEmitFStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeEmitFStatement))
// Normalize PGPG AST to 2-child layout (expressions, redirector).
// PGPG produces: 1+ children (emitf @a,@b,@c from with_adopted_grandchildren)
// or 2 children (emitf > file, @a,@b from children [Redirector, FcnArgs]).
var expressionsNode, redirectorNode *asts.ASTNode
switch {
case len(astNode.Children) >= 2 && astNode.Children[0].Type == asts.NodeType(NodeTypeRedirectWrite) ||
astNode.Children[0].Type == asts.NodeType(NodeTypeRedirectAppend) ||
astNode.Children[0].Type == asts.NodeType(NodeTypeRedirectPipe):
// Redirector variant: [Redirector, FcnArgs]
expressionsNode = astNode.Children[1]
redirectorNode = astNode.Children[0]
case len(astNode.Children) >= 1:
// No redirector: all children are emittables from FcnArgs
expressionsNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), astNode.Children)
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
default:
lib.InternalCodingErrorIf(true) // unexpected child count
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Things to be emitted, e.g. @a and @b in 'emitf > "foo.dat", @a, @b'.
@ -82,7 +98,7 @@ func (root *RootNode) BuildEmitFStatementNode(astNode *dsl.ASTNode) (IExecutable
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Redirection targets (the thing after > >> |, if any).
if redirectorNode.Type == dsl.NodeTypeNoOp {
if redirectorNode.Type == asts.NodeType(NodeTypeNoOp) {
// No > >> or | was provided.
retval.emitfToRedirectFunc = retval.emitfToRecordStream
} else {
@ -92,27 +108,31 @@ func (root *RootNode) BuildEmitFStatementNode(astNode *dsl.ASTNode) (IExecutable
redirectorTargetNode := redirectorNode.Children[0]
var err error
if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStdout {
if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetStdout) {
retval.emitfToRedirectFunc = retval.emitfToFileOrPipe
retval.outputHandlerManager = output.NewStdoutWriteHandlerManager(root.recordWriterOptions)
retval.redirectorTargetEvaluable = root.BuildStringLiteralNode("(stdout)")
} else if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStderr {
} else if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetStderr) {
retval.emitfToRedirectFunc = retval.emitfToFileOrPipe
retval.outputHandlerManager = output.NewStderrWriteHandlerManager(root.recordWriterOptions)
retval.redirectorTargetEvaluable = root.BuildStringLiteralNode("(stderr)")
} else {
retval.emitfToRedirectFunc = retval.emitfToFileOrPipe
retval.redirectorTargetEvaluable, err = root.BuildEvaluableNode(redirectorTargetNode)
targetNode := redirectorTargetNode
if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetRvalue) &&
redirectorTargetNode.Children != nil && len(redirectorTargetNode.Children) > 0 {
targetNode = redirectorTargetNode.Children[0]
}
retval.redirectorTargetEvaluable, err = root.BuildEvaluableNode(targetNode)
if err != nil {
return nil, err
}
if redirectorNode.Type == dsl.NodeTypeRedirectWrite {
if redirectorNode.Type == asts.NodeType(NodeTypeRedirectWrite) {
retval.outputHandlerManager = output.NewFileWritetHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectAppend {
} else if redirectorNode.Type == asts.NodeType(NodeTypeRedirectAppend) {
retval.outputHandlerManager = output.NewFileAppendHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectPipe {
} else if redirectorNode.Type == asts.NodeType(NodeTypeRedirectPipe) {
retval.outputHandlerManager = output.NewPipeWriteHandlerManager(root.recordWriterOptions)
} else {
return nil, fmt.Errorf("unhandled redirector node type %s", string(redirectorNode.Type))
@ -150,13 +170,13 @@ func (node *EmitFStatementNode) Execute(state *runtime.State) (*BlockExitPayload
//
// TODO: support indirects like 'emitf @[x."_sum"]'
func getNameFromNamedNode(astNode *dsl.ASTNode, description string) (string, error) {
if astNode.Type == dsl.NodeTypeDirectOosvarValue {
return string(astNode.Token.Lit), nil
} else if astNode.Type == dsl.NodeTypeLocalVariable {
return string(astNode.Token.Lit), nil
} else if astNode.Type == dsl.NodeTypeDirectFieldValue {
return string(astNode.Token.Lit), nil
func getNameFromNamedNode(astNode *asts.ASTNode, description string) (string, error) {
if astNode.Type == asts.NodeType(NodeTypeDirectOosvarValue) {
return tokenLitStripDollarOrAt(astNode), nil
} else if astNode.Type == asts.NodeType(NodeTypeLocalVariable) {
return tokenLit(astNode), nil
} else if astNode.Type == asts.NodeType(NodeTypeDirectFieldValue) {
return tokenLitStripDollarOrAt(astNode), nil
}
return "", fmt.Errorf(`can't get name of node type "%s" for %s`, string(astNode.Type), description)
}

View file

@ -8,21 +8,55 @@ package cst
import (
"os"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
type EnvironmentVariableNode struct {
nameEvaluable IEvaluable
}
func (root *RootNode) BuildEnvironmentVariableNode(astNode *dsl.ASTNode) (*EnvironmentVariableNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEnvironmentVariable)
func (root *RootNode) BuildEnvironmentVariableNode(astNode *asts.ASTNode) (*EnvironmentVariableNode, error) {
lib.InternalCodingErrorWithMessageIf(
astNode.Type != asts.NodeType(NodeTypeEnvironmentVariable) && string(astNode.Type) != NodeTypeEnvironmentVariable,
"expected EnvironmentVariable node",
)
lib.InternalCodingErrorIf(astNode.Children == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
nameEvaluable, err := root.BuildEvaluableNode(astNode.Children[0])
child := astNode.Children[0]
// Unwrap Parenthesized if present: ENV[("FOO")] or similar.
if string(child.Type) == NodeTypeParenthesized && child.Children != nil && len(child.Children) == 1 {
child = child.Children[0]
}
// ENV.FOO: child is non_sigil_name (identifier FOO) -> use as env var name directly
if string(child.Type) == "non_sigil_name" || string(child.Type) == NodeTypeLocalVariable {
sval := tokenLit(child)
if sval != "" {
return &EnvironmentVariableNode{
nameEvaluable: root.BuildStringLiteralNode(sval),
}, nil
}
}
// PGPG may put the string literal's text on the node or its child; extract it directly
// so we get the correct env var name even when BuildEvaluableNode would yield empty.
if string(child.Type) == NodeTypeStringLiteral || string(child.Type) == "string_literal" {
sval := tokenLit(child)
if sval == "" && child.Children != nil && len(child.Children) == 1 {
sval = tokenLit(child.Children[0])
}
if sval != "" {
// PGPG lexer may include surrounding quotes in the lexeme; strip them.
if len(sval) >= 2 && sval[0] == '"' && sval[len(sval)-1] == '"' {
sval = sval[1 : len(sval)-1]
}
return &EnvironmentVariableNode{
nameEvaluable: root.BuildStringLiteralNode(lib.UnbackslashStringLiteral(sval)),
}, nil
}
}
nameEvaluable, err := root.BuildEvaluableNode(child)
if err != nil {
return nil, err
}

View file

@ -8,64 +8,103 @@ import (
"fmt"
"os"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
func (root *RootNode) BuildEvaluableNode(astNode *dsl.ASTNode) (IEvaluable, error) {
if astNode.Children == nil {
return root.BuildLeafNode(astNode)
func (root *RootNode) BuildEvaluableNode(astNode *asts.ASTNode) (IEvaluable, error) {
// Try BuildLeafNode first for terminals
if astNode.Children == nil || len(astNode.Children) == 0 {
if leaf, err := root.BuildLeafNode(astNode); err == nil {
return leaf, nil
}
// Fall through to switch for leaf types BuildLeafNode doesn't know
}
switch astNode.Type {
case dsl.NodeTypeArrayLiteral: // [...]
case asts.NodeType(NodeTypeArrayLiteral): // [...]
return root.BuildArrayLiteralNode(astNode)
case dsl.NodeTypeMapLiteral: // {...}
case asts.NodeType(NodeTypeMapLiteral): // {...}
return root.BuildMapLiteralNode(astNode)
case dsl.NodeTypeArrayOrMapIndexAccess: // x[...]
case asts.NodeType(NodeTypeArrayOrMapIndexAccess): // x[...]
return root.BuildArrayOrMapIndexAccessNode(astNode)
case dsl.NodeTypeArraySliceAccess: // myarray[lo:hi]
case asts.NodeType(NodeTypeArraySliceLoHi), asts.NodeType(NodeTypeArraySliceHiOnly),
asts.NodeType(NodeTypeArraySliceLoOnly), asts.NodeType(NodeTypeArraySliceFull): // myarray[lo:hi]
return root.BuildArraySliceAccessNode(astNode)
case dsl.NodeTypeIndirectFieldValue: // $[...] (includes $[[n]] and $[[[n]]])
case asts.NodeType(NodeTypeIndirectFieldValue): // $[...] (includes $[[n]] and $[[[n]]])
return root.BuildIndirectFieldValueNode(astNode)
case dsl.NodeTypeIndirectOosvarValue: // $[...]
case asts.NodeType(NodeTypeIndirectOosvarValue): // $[...]
return root.BuildIndirectOosvarValueNode(astNode)
case dsl.NodeTypeEnvironmentVariable: // ENV["NAME"]
case asts.NodeType(NodeTypeEnvironmentVariable): // ENV["NAME"]
return root.BuildEnvironmentVariableNode(astNode)
// Operators are just functions with infix syntax so we treat them like
// functions in the CST. (The distinction between infix syntax, e.g.
// '1+2', and prefix syntax, e.g. 'plus(1,2)' disappears post-parse -- both
// parse to the same-shape AST.)
case dsl.NodeTypeOperator:
case asts.NodeType(NodeTypeOperator):
return root.BuildFunctionCallsiteNode(astNode)
case dsl.NodeTypeFunctionCallsite:
case asts.NodeType(NodeTypeFunctionCallsite):
return root.BuildFunctionCallsiteNode(astNode)
// The dot operator is a little different from other operators since it's
// type-dependent: for strings/int/bools etc it's just concatenation of
// string representations, but if the left-hand side is a map, it's a
// key-lookup with an unquoted literal on the right. E.g. mymap.foo is the
// same as mymap["foo"].
case dsl.NodeTypeDotOperator:
// The dot operator is a little different from other operators since it's
// type-dependent: for strings/int/bools etc it's just concatenation of
// string representations, but if the left-hand side is a map, it's a
// key-lookup with an unquoted literal on the right. E.g. mymap.foo is the
// same as mymap["foo"].
case asts.NodeType(NodeTypeDotOperator):
return root.BuildDotCallsiteNode(astNode)
case asts.NodeType(NodeTypeParenthesized):
// (expr) — unwrap and build the inner expression
lib.InternalCodingErrorIf(astNode.Children == nil || len(astNode.Children) != 1)
return root.BuildEvaluableNode(astNode.Children[0])
// Function literals like 'func (a,b) { return b - a }'
case dsl.NodeTypeUnnamedFunctionDefinition:
case asts.NodeType(NodeTypeUnnamedFunctionDefinition):
return root.BuildUnnamedUDFNode(astNode)
// Leaf/terminal types (PGPG may give them non-nil empty Children)
case asts.NodeType(NodeTypeDirectFieldValue), asts.NodeType(NodeTypeBracedFieldValue),
asts.NodeType(NodeTypeFullSrec), asts.NodeType(NodeTypeDirectOosvarValue),
asts.NodeType(NodeTypeBracedOosvarValue), asts.NodeType(NodeTypeFullOosvar),
asts.NodeType(NodeTypeLocalVariable),
asts.NodeType(NodeTypeIntLiteral), asts.NodeType(NodeTypeFloatLiteral),
asts.NodeType(NodeTypeStringLiteral), asts.NodeType(NodeTypeBoolLiteral),
asts.NodeType(NodeTypeNullLiteral), asts.NodeType(NodeTypeRegex):
return root.BuildLeafNode(astNode)
}
// Parenthesized: (expr) — unwrap and build the inner expression.
// Use string comparison in case asts.NodeType differs from astNode.Type's type.
if string(astNode.Type) == NodeTypeParenthesized && astNode.Children != nil && len(astNode.Children) == 1 {
return root.BuildEvaluableNode(astNode.Children[0])
}
// EnvironmentVariable: ENV["FOO"] or ENV.FOO
if string(astNode.Type) == NodeTypeEnvironmentVariable && astNode.Children != nil && len(astNode.Children) == 1 {
return root.BuildEnvironmentVariableNode(astNode)
}
// Fallback: try BuildLeafNode for unhandled types (e.g. DirectFieldValue, IntLiteral).
// Only for leaf-like nodes (0 or 1 child); nodes with 2+ children are not leaves.
if astNode.Children == nil || len(astNode.Children) <= 1 {
if leaf, err := root.BuildLeafNode(astNode); err == nil {
return leaf, nil
}
}
return nil, fmt.Errorf(
"at CST BuildEvaluableNode: unhandled AST node type %s", string(astNode.Type),
"at CST BuildEvaluableNode: unhandled AST node type %q (len=%d)", string(astNode.Type), len(astNode.Children),
)
}
@ -74,30 +113,24 @@ type IndirectFieldValueNode struct {
}
func (root *RootNode) BuildIndirectFieldValueNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeIndirectFieldValue)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeIndirectFieldValue))
lib.InternalCodingErrorIf(astNode.Children == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
child := astNode.Children[0]
if child.Type == dsl.NodeTypeArrayLiteral && len(child.Children) == 1 {
if child.Type == asts.NodeType(NodeTypeArrayLiteral) && len(child.Children) == 1 {
inner := child.Children[0]
if inner.Type == dsl.NodeTypeArrayLiteral && len(inner.Children) == 1 {
if inner.Type == asts.NodeType(NodeTypeArrayLiteral) && len(inner.Children) == 1 {
// $[[[n]]] → positional field value
indexASTNode := inner.Children[0]
syntheticAST := &dsl.ASTNode{
Type: dsl.NodeTypePositionalFieldValue,
Children: []*dsl.ASTNode{indexASTNode},
}
syntheticAST := asts.NewASTNode(nil, asts.NodeType(NodeTypePositionalFieldValue), []*asts.ASTNode{indexASTNode})
return root.BuildPositionalFieldValueNode(syntheticAST)
}
// $[[n]] → positional field name
indexASTNode := inner
syntheticAST := &dsl.ASTNode{
Type: dsl.NodeTypePositionalFieldName,
Children: []*dsl.ASTNode{indexASTNode},
}
syntheticAST := asts.NewASTNode(nil, asts.NodeType(NodeTypePositionalFieldName), []*asts.ASTNode{indexASTNode})
return root.BuildPositionalFieldNameNode(syntheticAST)
}
@ -145,9 +178,9 @@ type IndirectOosvarValueNode struct {
}
func (root *RootNode) BuildIndirectOosvarValueNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (*IndirectOosvarValueNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeIndirectOosvarValue)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeIndirectOosvarValue))
lib.InternalCodingErrorIf(astNode.Children == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
oosvarNameEvaluable, err := root.BuildEvaluableNode(astNode.Children[0])

View file

@ -17,9 +17,9 @@
package cst
import (
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// BareBooleanStatementNode is for implicit filter statements such as mlr
@ -31,8 +31,8 @@ type BareBooleanStatementNode struct {
writeToState bool
}
func (root *RootNode) BuildBareBooleanStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeBareBoolean)
func (root *RootNode) BuildBareBooleanStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeBareBoolean))
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
bareBooleanEvaluable, err := root.BuildEvaluableNode(astNode.Children[0])
@ -65,8 +65,8 @@ type FilterStatementNode struct {
filterEvaluable IEvaluable
}
func (root *RootNode) BuildFilterStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeFilterStatement)
func (root *RootNode) BuildFilterStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeFilterStatement))
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
filterEvaluable, err := root.BuildEvaluableNode(astNode.Children[0])

View file

@ -5,11 +5,11 @@ package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/parsing/token"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
"github.com/johnkerl/pgpg/go/lib/pkg/tokens"
)
// Sample AST:
@ -65,17 +65,17 @@ func NewForLoopOneVariableNode(
// * LocalVariable "k"
// * LocalVariable "v"
func (root *RootNode) BuildForLoopOneVariableNode(astNode *dsl.ASTNode) (*ForLoopOneVariableNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeForLoopOneVariable)
func (root *RootNode) BuildForLoopOneVariableNode(astNode *asts.ASTNode) (*ForLoopOneVariableNode, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeForLoopOneVariable))
lib.InternalCodingErrorIf(len(astNode.Children) != 3)
variableASTNode := astNode.Children[0]
indexableASTNode := astNode.Children[1]
blockASTNode := astNode.Children[2]
lib.InternalCodingErrorIf(variableASTNode.Type != dsl.NodeTypeLocalVariable)
lib.InternalCodingErrorIf(variableASTNode.Type != asts.NodeType(NodeTypeLocalVariable))
lib.InternalCodingErrorIf(variableASTNode.Token == nil)
variableName := string(variableASTNode.Token.Lit)
variableName := tokenLit(variableASTNode)
// TODO: error if loop-over node isn't map/array (inasmuch as can be
// detected at CST-build time)
@ -237,8 +237,8 @@ func NewForLoopTwoVariableNode(
// * LocalVariable "k"
// * LocalVariable "v"
func (root *RootNode) BuildForLoopTwoVariableNode(astNode *dsl.ASTNode) (*ForLoopTwoVariableNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeForLoopTwoVariable)
func (root *RootNode) BuildForLoopTwoVariableNode(astNode *asts.ASTNode) (*ForLoopTwoVariableNode, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeForLoopTwoVariable))
lib.InternalCodingErrorIf(len(astNode.Children) != 4)
keyVariableASTNode := astNode.Children[0]
@ -246,14 +246,14 @@ func (root *RootNode) BuildForLoopTwoVariableNode(astNode *dsl.ASTNode) (*ForLoo
indexableASTNode := astNode.Children[2]
blockASTNode := astNode.Children[3]
lib.InternalCodingErrorIf(keyVariableASTNode.Type != dsl.NodeTypeLocalVariable)
lib.InternalCodingErrorIf(keyVariableASTNode.Type != asts.NodeType(NodeTypeLocalVariable))
lib.InternalCodingErrorIf(keyVariableASTNode.Token == nil)
keyVariableName := string(keyVariableASTNode.Token.Lit)
keyVariableName := tokenLit(keyVariableASTNode)
keyIndexVariable := runtime.NewStackVariable(keyVariableName)
lib.InternalCodingErrorIf(valueVariableASTNode.Type != dsl.NodeTypeLocalVariable)
lib.InternalCodingErrorIf(valueVariableASTNode.Type != asts.NodeType(NodeTypeLocalVariable))
lib.InternalCodingErrorIf(valueVariableASTNode.Token == nil)
valueVariableName := string(valueVariableASTNode.Token.Lit)
valueVariableName := tokenLit(valueVariableASTNode)
valueIndexVariable := runtime.NewStackVariable(valueVariableName)
// TODO: error if loop-over node isn't map/array (inasmuch as can be
@ -425,9 +425,9 @@ func NewForLoopMultivariableNode(
// * statement block
func (root *RootNode) BuildForLoopMultivariableNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (*ForLoopMultivariableNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeForLoopMultivariable)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeForLoopMultivariable))
lib.InternalCodingErrorIf(len(astNode.Children) != 4)
keyVariablesASTNode := astNode.Children[0]
@ -435,18 +435,20 @@ func (root *RootNode) BuildForLoopMultivariableNode(
indexableASTNode := astNode.Children[2]
blockASTNode := astNode.Children[3]
lib.InternalCodingErrorIf(keyVariablesASTNode.Type != dsl.NodeTypeParameterList)
// PGPG produces MultiIndex; legacy produced ParameterList. Both have LocalVariable children.
lib.InternalCodingErrorIf(keyVariablesASTNode.Type != asts.NodeType(NodeTypeParameterList) &&
keyVariablesASTNode.Type != asts.NodeType(NodeTypeMultiIndex))
lib.InternalCodingErrorIf(keyVariablesASTNode.Children == nil)
keyIndexVariables := make([]*runtime.StackVariable, len(keyVariablesASTNode.Children))
for i, keyVariableASTNode := range keyVariablesASTNode.Children {
lib.InternalCodingErrorIf(keyVariableASTNode.Token == nil)
keyIndexVariableName := string(keyVariableASTNode.Token.Lit)
keyIndexVariableName := tokenLit(keyVariableASTNode)
keyIndexVariables[i] = runtime.NewStackVariable(keyIndexVariableName)
}
lib.InternalCodingErrorIf(valueVariableASTNode.Type != dsl.NodeTypeLocalVariable)
lib.InternalCodingErrorIf(valueVariableASTNode.Type != asts.NodeType(NodeTypeLocalVariable))
lib.InternalCodingErrorIf(valueVariableASTNode.Token == nil)
valueVariableName := string(valueVariableASTNode.Token.Lit)
valueVariableName := tokenLit(valueVariableASTNode)
valueIndexVariable := runtime.NewStackVariable(valueVariableName)
// TODO: error if loop-over node isn't map/array (inasmuch as can be
@ -693,7 +695,7 @@ type TripleForLoopNode struct {
startBlockNode *StatementBlockNode
precontinuationAssignments []IExecutable
continuationExpressionNode IEvaluable
continuationExpressionToken *token.Token
continuationExpressionToken *tokens.Token
updateBlockNode *StatementBlockNode
bodyBlockNode *StatementBlockNode
}
@ -702,7 +704,7 @@ func NewTripleForLoopNode(
startBlockNode *StatementBlockNode,
precontinuationAssignments []IExecutable,
continuationExpressionNode IEvaluable,
continuationExpressionToken *token.Token,
continuationExpressionToken *tokens.Token,
updateBlockNode *StatementBlockNode,
bodyBlockNode *StatementBlockNode,
) *TripleForLoopNode {
@ -756,8 +758,8 @@ func NewTripleForLoopNode(
// * DirectFieldValue "i"
// * LocalVariable "i"
func (root *RootNode) BuildTripleForLoopNode(astNode *dsl.ASTNode) (*TripleForLoopNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeTripleForLoop)
func (root *RootNode) BuildTripleForLoopNode(astNode *asts.ASTNode) (*TripleForLoopNode, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeTripleForLoop))
lib.InternalCodingErrorIf(len(astNode.Children) != 4)
startBlockASTNode := astNode.Children[0]
@ -765,10 +767,16 @@ func (root *RootNode) BuildTripleForLoopNode(astNode *dsl.ASTNode) (*TripleForLo
updateBlockASTNode := astNode.Children[2]
bodyBlockASTNode := astNode.Children[3]
lib.InternalCodingErrorIf(startBlockASTNode.Type != dsl.NodeTypeStatementBlock)
lib.InternalCodingErrorIf(continuationExpressionASTNode.Type != dsl.NodeTypeStatementBlock)
lib.InternalCodingErrorIf(updateBlockASTNode.Type != dsl.NodeTypeStatementBlock)
lib.InternalCodingErrorIf(bodyBlockASTNode.Type != dsl.NodeTypeStatementBlock)
// PGPG: body is StatementBlockInBraces; unwrap to get StatementBlock
if bodyBlockASTNode.Type == asts.NodeType(NodeTypeStatementBlockInBraces) &&
len(bodyBlockASTNode.Children) == 1 {
bodyBlockASTNode = bodyBlockASTNode.Children[0]
}
lib.InternalCodingErrorIf(startBlockASTNode.Type != asts.NodeType(NodeTypeStatementBlock))
lib.InternalCodingErrorIf(continuationExpressionASTNode.Type != asts.NodeType(NodeTypeStatementBlock))
lib.InternalCodingErrorIf(updateBlockASTNode.Type != asts.NodeType(NodeTypeStatementBlock))
lib.InternalCodingErrorIf(bodyBlockASTNode.Type != asts.NodeType(NodeTypeStatementBlock))
startBlockNode, err := root.BuildStatementBlockNode(startBlockASTNode)
if err != nil {
@ -780,20 +788,26 @@ func (root *RootNode) BuildTripleForLoopNode(astNode *dsl.ASTNode) (*TripleForLo
// for (int i = 0; c += 1, i < 10; i += 1) { ... }
var precontinuationAssignments []IExecutable = nil
var continuationExpressionNode IEvaluable = nil
var continuationExpressionToken *token.Token = nil
var continuationExpressionToken *tokens.Token = nil
if len(continuationExpressionASTNode.Children) > 0 { // empty is true
n := len(continuationExpressionASTNode.Children)
if n > 1 {
precontinuationAssignments = make([]IExecutable, n-1)
for i := 0; i < n-1; i++ {
if continuationExpressionASTNode.Children[i].Type != dsl.NodeTypeAssignment {
childType := continuationExpressionASTNode.Children[i].Type
if childType != asts.NodeType(NodeTypeAssignment) &&
childType != asts.NodeType(NodeTypeCompoundAssignment) {
return nil, fmt.Errorf(
"the non-final triple-for continuation statements must be assignments",
)
}
precontinuationAssignment, err := root.BuildAssignmentNode(
continuationExpressionASTNode.Children[i],
)
var precontinuationAssignment IExecutable
var err error
if childType == asts.NodeType(NodeTypeCompoundAssignment) {
precontinuationAssignment, err = root.BuildCompoundAssignmentNode(continuationExpressionASTNode.Children[i])
} else {
precontinuationAssignment, err = root.BuildAssignmentNode(continuationExpressionASTNode.Children[i])
}
if err != nil {
return nil, err
}
@ -802,7 +816,7 @@ func (root *RootNode) BuildTripleForLoopNode(astNode *dsl.ASTNode) (*TripleForLo
}
bareBooleanASTNode := continuationExpressionASTNode.Children[n-1]
if bareBooleanASTNode.Type != dsl.NodeTypeBareBoolean {
if bareBooleanASTNode.Type != asts.NodeType(NodeTypeBareBoolean) {
if n == 1 {
return nil, fmt.Errorf(
"the triple-for continuation statement must be a bare boolean",
@ -881,7 +895,7 @@ func (node *TripleForLoopNode) Execute(state *runtime.State) (*BlockExitPayload,
if !isBool {
return nil, fmt.Errorf(
"for-loop continuation did not evaluate to boolean%s",
dsl.TokenToLocationInfo(node.continuationExpressionToken),
pgpgTokenToLocationInfo(node.continuationExpressionToken),
)
}
if !boolValue {

View file

@ -7,8 +7,8 @@
package cst
import (
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// Function lookup:
@ -21,15 +21,20 @@ import (
// o On a next pass, we will walk that list resolving against all encountered
// UDF definitions. (It will be an error then if it's still unresolvable.)
func (root *RootNode) BuildFunctionCallsiteNode(astNode *dsl.ASTNode) (IEvaluable, error) {
func (root *RootNode) BuildFunctionCallsiteNode(astNode *asts.ASTNode) (IEvaluable, error) {
lib.InternalCodingErrorIf(
astNode.Type != dsl.NodeTypeFunctionCallsite &&
astNode.Type != dsl.NodeTypeOperator,
astNode.Type != asts.NodeType(NodeTypeFunctionCallsite) &&
astNode.Type != asts.NodeType(NodeTypeOperator),
)
lib.InternalCodingErrorIf(astNode.Token == nil)
lib.InternalCodingErrorIf(astNode.Children == nil)
functionName := string(astNode.Token.Lit)
functionName := tokenLit(astNode)
// PGPG produces ternary (cond ? a : b) with "?" token; builtin is registered as "?:"
if functionName == "?" && len(astNode.Children) == 3 {
functionName = "?:"
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Special-case the dot operator, which is:

View file

@ -5,11 +5,11 @@ package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/parsing/token"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
"github.com/johnkerl/pgpg/go/lib/pkg/tokens"
)
type IfChainNode struct {
@ -26,7 +26,7 @@ func NewIfChainNode(ifItems []*IfItem) *IfChainNode {
// statement-block part {...}. For "else", the conditional is nil.
type IfItem struct {
conditionNode IEvaluable
conditionToken *token.Token
conditionToken *tokens.Token
statementBlockNode *StatementBlockNode
}
@ -67,16 +67,16 @@ type IfItem struct {
// * DirectFieldValue "z"
// * IntLiteral "900"
func (root *RootNode) BuildIfChainNode(astNode *dsl.ASTNode) (*IfChainNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeIfChain)
func (root *RootNode) BuildIfChainNode(astNode *asts.ASTNode) (*IfChainNode, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeIfChain))
ifItems := []*IfItem{}
astChildren := astNode.Children
for _, astChild := range astChildren {
lib.InternalCodingErrorIf(astChild.Type != dsl.NodeTypeIfItem)
token := string(astChild.Token.Lit) // "if", "elif", "else"
lib.InternalCodingErrorIf(astChild.Type != asts.NodeType(NodeTypeIfItem))
token := tokenLit(astChild) // "if", "elif", "else"
if token == "if" || token == "elif" {
lib.InternalCodingErrorIf(len(astChild.Children) != 2)
conditionNode, err := root.BuildEvaluableNode(astChild.Children[0])
@ -125,7 +125,7 @@ func (node *IfChainNode) Execute(state *runtime.State) (*BlockExitPayload, error
if !isBool {
return nil, fmt.Errorf(
"conditional expression did not evaluate to boolean%s",
dsl.TokenToLocationInfo(ifItem.conditionToken),
pgpgTokenToLocationInfo(ifItem.conditionToken),
)
}
if boolValue {

View file

@ -5,78 +5,127 @@ package cst
import (
"fmt"
"math"
"strings"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
func (root *RootNode) BuildLeafNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Children != nil)
sval := string(astNode.Token.Lit)
// The BNF uses empty slice for terminals. It may also produce reduced literal nodes
// (float_literal, int_literal) with one child (the terminal).
lib.InternalCodingErrorIf(astNode.Children != nil && len(astNode.Children) > 1)
sval := tokenLit(astNode)
if sval == "" && astNode.Children != nil && len(astNode.Children) == 1 {
sval = tokenLit(astNode.Children[0])
}
switch astNode.Type {
case dsl.NodeTypeDirectFieldValue:
return root.BuildDirectFieldRvalueNode(sval), nil
case dsl.NodeTypeFullSrec:
case asts.NodeType(NodeTypeDirectFieldValue):
return root.BuildDirectFieldRvalueNode(tokenLitStripDollarOrAt(astNode)), nil
case asts.NodeType(NodeTypeFullSrec):
return root.BuildFullSrecRvalueNode(), nil
case dsl.NodeTypeDirectOosvarValue:
return root.BuildDirectOosvarRvalueNode(sval), nil
case dsl.NodeTypeFullOosvar:
case asts.NodeType(NodeTypeDirectOosvarValue):
return root.BuildDirectOosvarRvalueNode(tokenLitStripDollarOrAt(astNode)), nil
case asts.NodeType(NodeTypeFullOosvar):
return root.BuildFullOosvarRvalueNode(), nil
case dsl.NodeTypeLocalVariable:
case asts.NodeType(NodeTypeLocalVariable):
return root.BuildLocalVariableNode(sval), nil
case dsl.NodeTypeStringLiteral:
case asts.NodeType(NodeTypeStringLiteral):
return root.BuildStringLiteralNode(sval), nil
case dsl.NodeTypeRegex:
case asts.NodeType(NodeTypeRegex):
// During the BNF parse all string literals -- "foo" or "(..)_(...)"
// regexes etc -- are marked as dsl.NodeTypeStringLiteral. However, a
// regexes etc -- are marked as asts.NodeType(NodeTypeStringLiteral). However, a
// CST-build pre-pass relabels second argument to sub/gsub etc -- any
// known regex positions -- from dsl.NodeTypeStringLiteral to
// dsl.NodeTypeRegex. The RegexLiteralNode is responsible for
// known regex positions -- from asts.NodeType(NodeTypeStringLiteral) to
// asts.NodeType(NodeTypeRegex). The RegexLiteralNode is responsible for
// handling backslash sequences for regex literals differently from
// those for non-regex string literals.
return root.BuildRegexLiteralNode(sval), nil
case dsl.NodeTypeRegexCaseInsensitive:
// StringLiteral nodes like '"abc"' entered by the user come in from
// the AST as 'abc', with double quotes removed. Case-insensitive
// regexes like '"a.*b"i' come in with initial '"' and final '"i'
// intact. We let the sub/regextract/etc functions deal with this.
// (The alternative would be to make a separate Mlrval type separate
// from string.)
case asts.NodeType(NodeTypeRegexCaseInsensitive):
// PGPG: RegexCaseInsensitive has string_literal child. CompileMillerRegex
// expects "a.*b"i format for case-insensitive; append "i" if not present.
if sval == "" && astNode.Children != nil && len(astNode.Children) > 0 {
sval = tokenLit(astNode.Children[0])
}
if sval != "" && !strings.HasSuffix(sval, "i") {
sval = sval + "i"
}
return root.BuildRegexLiteralNode(sval), nil
case dsl.NodeTypeIntLiteral:
case asts.NodeType(NodeTypeIntLiteral):
return root.BuildIntLiteralNode(sval), nil
case dsl.NodeTypeFloatLiteral:
case asts.NodeType(NodeTypeFloatLiteral):
return root.BuildFloatLiteralNode(sval), nil
case dsl.NodeTypeBoolLiteral:
case asts.NodeType(NodeTypeBoolLiteral):
return root.BuildBoolLiteralNode(sval), nil
case dsl.NodeTypeNullLiteral:
case asts.NodeType(NodeTypeNullLiteral):
return root.BuildNullLiteralNode(), nil
case dsl.NodeTypeContextVariable:
case asts.NodeType(NodeTypeContextVariable):
return root.BuildContextVariableNode(astNode)
case dsl.NodeTypeConstant:
case asts.NodeType(NodeTypeConstant):
return root.BuildConstantNode(astNode)
case dsl.NodeTypeArraySliceEmptyLowerIndex:
case asts.NodeType(NodeTypeArraySliceEmptyLowerIndex):
return root.BuildArraySliceEmptyLowerIndexNode(astNode)
case dsl.NodeTypeArraySliceEmptyUpperIndex:
case asts.NodeType(NodeTypeArraySliceEmptyUpperIndex):
return root.BuildArraySliceEmptyUpperIndexNode(astNode)
case dsl.NodeTypePanic:
case asts.NodeType(NodeTypePanic):
return root.BuildPanicNode(astNode)
}
// PGPG may use different type strings; try string comparison for known leaf types
switch string(astNode.Type) {
case "DirectFieldValue":
return root.BuildDirectFieldRvalueNode(tokenLitStripDollarOrAt(astNode)), nil
case "FullSrec":
return root.BuildFullSrecRvalueNode(), nil
case "DirectOosvarValue":
return root.BuildDirectOosvarRvalueNode(tokenLitStripDollarOrAt(astNode)), nil
case "BracedFieldValue":
return root.BuildDirectFieldRvalueNode(tokenLitStripBraced(astNode)), nil
case "FullOosvar":
return root.BuildFullOosvarRvalueNode(), nil
case "BracedOosvarValue":
return root.BuildDirectOosvarRvalueNode(tokenLitStripBraced(astNode)), nil
case "LocalVariable":
return root.BuildLocalVariableNode(sval), nil
case "string_literal":
return root.BuildStringLiteralNode(sval), nil
case "int_literal":
return root.BuildIntLiteralNode(sval), nil
case "float_literal":
return root.BuildFloatLiteralNode(sval), nil
case "bool_literal":
return root.BuildBoolLiteralNode(sval), nil
case "null_literal":
return root.BuildNullLiteralNode(), nil
case "RegexCaseInsensitive", "regex_case_insensitive":
if sval == "" && astNode.Children != nil && len(astNode.Children) > 0 {
sval = tokenLit(astNode.Children[0])
}
if sval != "" && !strings.HasSuffix(sval, "i") {
sval = sval + "i"
}
return root.BuildRegexLiteralNode(sval), nil
}
// PGPG produces ctx_NF, ctx_NR, etc. as terminal types; treat as ContextVariable
if strings.HasPrefix(string(astNode.Type), "ctx_") {
return root.BuildContextVariableNode(astNode)
}
return nil, fmt.Errorf("at CST BuildLeafNode: unhandled AST node %s", string(astNode.Type))
}
@ -203,9 +252,9 @@ func (node *LocalVariableNode) Evaluate(
}
// During the BNF parse all string literals -- "foo" or "(..)_(...)" regexes
// etc -- are marked as dsl.NodeTypeStringLiteral. However, a CST-build
// etc -- are marked as asts.NodeType(NodeTypeStringLiteral). However, a CST-build
// pre-pass relabels second argument to sub/gsub etc -- any known regex
// positions -- from dsl.NodeTypeStringLiteral to dsl.NodeTypeRegex. The
// positions -- from asts.NodeType(NodeTypeStringLiteral) to asts.NodeType(NodeTypeRegex). The
// RegexLiteralNode is responsible for handling backslash sequences for
// regex literals differently from those for non-regex string literals.
@ -252,6 +301,11 @@ type RegexCaptureReplacementNode struct {
}
func (root *RootNode) BuildStringLiteralNode(literal string) IEvaluable {
// The PGPG lexer produces string_literal token with surrounding quotes in the lexeme.
// Strip them so "a" becomes a.
if len(literal) >= 2 && literal[0] == '"' && literal[len(literal)-1] == '"' {
literal = literal[1 : len(literal)-1]
}
// Convert "\t" to tab character, "\"" to double-quote character, etc.
// This is intentionally done for StringLiteralNode but not for
// RegexLiteralNode. See also https://github.com/johnkerl/miller/issues/297.
@ -379,9 +433,9 @@ func (node *MlrvalLiteralNode) Evaluate(
return node.literal
}
func (root *RootNode) BuildContextVariableNode(astNode *dsl.ASTNode) (IEvaluable, error) {
func (root *RootNode) BuildContextVariableNode(astNode *asts.ASTNode) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Token == nil)
sval := string(astNode.Token.Lit)
sval := tokenLit(astNode)
switch sval {
@ -562,9 +616,9 @@ func (node *FLATSEPNode) Evaluate(
return mlrval.FromString(state.Options.WriterOptions.FLATSEP)
}
func (root *RootNode) BuildConstantNode(astNode *dsl.ASTNode) (IEvaluable, error) {
func (root *RootNode) BuildConstantNode(astNode *asts.ASTNode) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Token == nil)
sval := string(astNode.Token.Lit)
sval := tokenLit(astNode)
switch sval {
@ -608,9 +662,9 @@ type LiteralOneNode struct {
// In array slices like 'myarray[:4]', the lower index is always 1 since Miller
// user-space indices are 1-up.
func (root *RootNode) BuildArraySliceEmptyLowerIndexNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (*LiteralOneNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeArraySliceEmptyLowerIndex)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeArraySliceEmptyLowerIndex))
return &LiteralOneNode{}, nil
}
func (node *LiteralOneNode) Evaluate(
@ -627,9 +681,9 @@ type LiteralEmptyStringNode struct {
// we don't have access to the array length in this AST node so we return ""
// so the slice-index CST node can compute it.
func (root *RootNode) BuildArraySliceEmptyUpperIndexNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (*LiteralEmptyStringNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeArraySliceEmptyUpperIndex)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeArraySliceEmptyUpperIndex))
return &LiteralEmptyStringNode{}, nil
}
func (node *LiteralEmptyStringNode) Evaluate(
@ -645,7 +699,7 @@ func (node *LiteralEmptyStringNode) Evaluate(
type PanicNode struct {
}
func (root *RootNode) BuildPanicNode(astNode *dsl.ASTNode) (*PanicNode, error) {
func (root *RootNode) BuildPanicNode(astNode *asts.ASTNode) (*PanicNode, error) {
return &PanicNode{}, nil
}
func (node *PanicNode) Evaluate(

View file

@ -8,43 +8,46 @@ import (
"fmt"
"os"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
func (root *RootNode) BuildAssignableNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IAssignable, error) {
switch astNode.Type {
case dsl.NodeTypeDirectFieldValue:
case asts.NodeType(NodeTypeDirectFieldValue), asts.NodeType(NodeTypeBracedFieldValue):
return root.BuildDirectFieldValueLvalueNode(astNode)
case dsl.NodeTypeIndirectFieldValue:
case asts.NodeType(NodeTypeIndirectFieldValue):
return root.BuildIndirectFieldValueLvalueNode(astNode)
case dsl.NodeTypeFullSrec:
case asts.NodeType(NodeTypeFullSrec):
return root.BuildFullSrecLvalueNode(astNode)
case dsl.NodeTypeDirectOosvarValue:
case asts.NodeType(NodeTypeDirectOosvarValue), asts.NodeType(NodeTypeBracedOosvarValue):
return root.BuildDirectOosvarValueLvalueNode(astNode)
case dsl.NodeTypeIndirectOosvarValue:
case asts.NodeType(NodeTypeIndirectOosvarValue):
return root.BuildIndirectOosvarValueLvalueNode(astNode)
case dsl.NodeTypeFullOosvar:
case asts.NodeType(NodeTypeFullOosvar):
return root.BuildFullOosvarLvalueNode(astNode)
case dsl.NodeTypeLocalVariable:
case asts.NodeType(NodeTypeLocalVariable):
return root.BuildLocalVariableLvalueNode(astNode)
case dsl.NodeTypeArrayOrMapIndexAccess:
case asts.NodeType(NodeTypeArrayOrMapIndexAccess):
return root.BuildIndexedLvalueNode(astNode)
case dsl.NodeTypeDotOperator:
case asts.NodeType(NodeTypeDotOperator):
return root.BuildIndexedLvalueNode(astNode)
case dsl.NodeTypeEnvironmentVariable:
case asts.NodeType(NodeTypeEnvironmentVariable):
return root.BuildEnvironmentVariableLvalueNode(astNode)
case asts.NodeType("TypedeclLocalVariable"):
return root.BuildTypedeclLocalVariableLvalueNode(astNode)
}
return nil, errors.New(
@ -57,11 +60,18 @@ type DirectFieldValueLvalueNode struct {
}
func (root *RootNode) BuildDirectFieldValueLvalueNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeDirectFieldValue)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeDirectFieldValue) &&
astNode.Type != asts.NodeType(NodeTypeBracedFieldValue))
lhsFieldName := mlrval.FromString(string(astNode.Token.Lit))
var name string
if astNode.Type == asts.NodeType(NodeTypeBracedFieldValue) {
name = tokenLitStripBraced(astNode)
} else {
name = tokenLitStripDollarOrAt(astNode)
}
lhsFieldName := mlrval.FromString(name)
return NewDirectFieldValueLvalueNode(lhsFieldName), nil
}
@ -144,30 +154,24 @@ type IndirectFieldValueLvalueNode struct {
}
func (root *RootNode) BuildIndirectFieldValueLvalueNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeIndirectFieldValue)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeIndirectFieldValue))
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
child := astNode.Children[0]
if child.Type == dsl.NodeTypeArrayLiteral && len(child.Children) == 1 {
if child.Type == asts.NodeType(NodeTypeArrayLiteral) && len(child.Children) == 1 {
inner := child.Children[0]
if inner.Type == dsl.NodeTypeArrayLiteral && len(inner.Children) == 1 {
if inner.Type == asts.NodeType(NodeTypeArrayLiteral) && len(inner.Children) == 1 {
// $[[[n]]] → positional field value lvalue
indexASTNode := inner.Children[0]
syntheticAST := &dsl.ASTNode{
Type: dsl.NodeTypePositionalFieldValue,
Children: []*dsl.ASTNode{indexASTNode},
}
syntheticAST := asts.NewASTNode(nil, asts.NodeType(NodeTypePositionalFieldValue), []*asts.ASTNode{indexASTNode})
return root.BuildPositionalFieldValueLvalueNode(syntheticAST)
}
// $[[n]] → positional field name lvalue
indexASTNode := inner
syntheticAST := &dsl.ASTNode{
Type: dsl.NodeTypePositionalFieldName,
Children: []*dsl.ASTNode{indexASTNode},
}
syntheticAST := asts.NewASTNode(nil, asts.NodeType(NodeTypePositionalFieldName), []*asts.ASTNode{indexASTNode})
return root.BuildPositionalFieldNameLvalueNode(syntheticAST)
}
@ -264,9 +268,9 @@ type PositionalFieldNameLvalueNode struct {
}
func (root *RootNode) BuildPositionalFieldNameLvalueNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypePositionalFieldName)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypePositionalFieldName))
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
lhsFieldIndexExpression, err := root.BuildEvaluableNode(astNode.Children[0])
@ -372,9 +376,9 @@ type PositionalFieldValueLvalueNode struct {
}
func (root *RootNode) BuildPositionalFieldValueLvalueNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypePositionalFieldValue)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypePositionalFieldValue))
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
lhsFieldIndexExpression, err := root.BuildEvaluableNode(astNode.Children[0])
@ -485,10 +489,11 @@ func (node *PositionalFieldValueLvalueNode) UnassignIndexed(
type FullSrecLvalueNode struct {
}
func (root *RootNode) BuildFullSrecLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeFullSrec)
func (root *RootNode) BuildFullSrecLvalueNode(astNode *asts.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeFullSrec))
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(astNode.Children != nil)
// PGPG FullSrec has empty list as children
lib.InternalCodingErrorIf(astNode.Children != nil && len(astNode.Children) > 0)
return NewFullSrecLvalueNode(), nil
}
@ -559,10 +564,17 @@ type DirectOosvarValueLvalueNode struct {
lhsOosvarName *mlrval.Mlrval
}
func (root *RootNode) BuildDirectOosvarValueLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeDirectOosvarValue)
func (root *RootNode) BuildDirectOosvarValueLvalueNode(astNode *asts.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeDirectOosvarValue) &&
astNode.Type != asts.NodeType(NodeTypeBracedOosvarValue))
lhsOosvarName := mlrval.FromString(string(astNode.Token.Lit))
var name string
if astNode.Type == asts.NodeType(NodeTypeBracedOosvarValue) {
name = tokenLitStripBraced(astNode)
} else {
name = tokenLitStripDollarOrAt(astNode)
}
lhsOosvarName := mlrval.FromString(name)
return NewDirectOosvarValueLvalueNode(lhsOosvarName), nil
}
@ -624,9 +636,9 @@ type IndirectOosvarValueLvalueNode struct {
}
func (root *RootNode) BuildIndirectOosvarValueLvalueNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeIndirectOosvarValue)
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeIndirectOosvarValue))
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
@ -702,10 +714,10 @@ func (node *IndirectOosvarValueLvalueNode) UnassignIndexed(
type FullOosvarLvalueNode struct {
}
func (root *RootNode) BuildFullOosvarLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeFullOosvar)
func (root *RootNode) BuildFullOosvarLvalueNode(astNode *asts.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeFullOosvar))
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(astNode.Children != nil)
lib.InternalCodingErrorIf(astNode.Children != nil && len(astNode.Children) > 0)
return NewFullOosvarLvalueNode(), nil
}
@ -767,25 +779,34 @@ type LocalVariableLvalueNode struct {
defineTypedAtScope bool
}
func (root *RootNode) BuildLocalVariableLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeLocalVariable)
func (root *RootNode) BuildTypedeclLocalVariableLvalueNode(astNode *asts.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Children == nil || len(astNode.Children) < 2)
typeNode := astNode.Children[0]
varNode := astNode.Children[1]
// PGPG Typedecl produces kw_int, kw_bool, etc. (no Typedecl wrapper node)
lib.InternalCodingErrorIf(varNode.Type != asts.NodeType(NodeTypeLocalVariable))
typeName := tokenLit(typeNode)
variableName := tokenLit(varNode)
return NewLocalVariableLvalueNode(
runtime.NewStackVariable(variableName),
typeName,
true, // defineTypedAtScope
), nil
}
func (root *RootNode) BuildLocalVariableLvalueNode(astNode *asts.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeLocalVariable))
// TODO require type mask in strict mode
variableName := string(astNode.Token.Lit)
variableName := tokenLit(astNode)
typeName := "any"
defineTypedAtScope := false
if astNode.Children == nil { // untyped, like 'x = 3'
if root.strictMode {
return nil, fmt.Errorf(
`need typedecl such as "var", "str", "num", etc. for variable "%s" in strict mode`,
variableName,
)
}
} else { // typed, like 'num x = 3'
// PGPG: LocalVariable is terminal (children nil or empty). Miller had typed params with Children[0]=Typedecl.
if astNode.Children != nil && len(astNode.Children) > 0 { // typed, like 'num x = 3'
typeNode := astNode.Children[0]
lib.InternalCodingErrorIf(typeNode.Type != dsl.NodeTypeTypedecl)
typeName = string(typeNode.Token.Lit)
lib.InternalCodingErrorIf(typeNode.Type != asts.NodeType(NodeTypeTypedecl))
typeName = tokenLit(typeNode)
defineTypedAtScope = true
}
return NewLocalVariableLvalueNode(
@ -870,8 +891,8 @@ type IndexedLvalueNode struct {
// Either 'mymap["attr"]' or 'mymap.attr'. Furthermore they can be mixed as in
// 'mymap["foo"].bar' or 'mymap.foo["bar"]'.
func (root *RootNode) BuildIndexedLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeArrayOrMapIndexAccess && astNode.Type != dsl.NodeTypeDotOperator)
func (root *RootNode) BuildIndexedLvalueNode(astNode *asts.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeArrayOrMapIndexAccess) && astNode.Type != asts.NodeType(NodeTypeDotOperator))
lib.InternalCodingErrorIf(astNode == nil)
var baseLvalue IAssignable = nil
@ -895,7 +916,7 @@ func (root *RootNode) BuildIndexedLvalueNode(astNode *dsl.ASTNode) (IAssignable,
// then the base Lvalue.
walkerNode := astNode
for {
if walkerNode.Type == dsl.NodeTypeArrayOrMapIndexAccess {
if walkerNode.Type == asts.NodeType(NodeTypeArrayOrMapIndexAccess) {
lib.InternalCodingErrorIf(walkerNode == nil)
lib.InternalCodingErrorIf(len(walkerNode.Children) != 2)
indexEvaluable, err := root.BuildEvaluableNode(walkerNode.Children[1])
@ -904,10 +925,10 @@ func (root *RootNode) BuildIndexedLvalueNode(astNode *dsl.ASTNode) (IAssignable,
}
indexEvaluables = append([]IEvaluable{indexEvaluable}, indexEvaluables...)
walkerNode = walkerNode.Children[0]
} else if walkerNode.Type == dsl.NodeTypeDotOperator {
} else if walkerNode.Type == asts.NodeType(NodeTypeDotOperator) {
lib.InternalCodingErrorIf(walkerNode == nil)
lib.InternalCodingErrorIf(len(walkerNode.Children) != 2)
indexEvaluable := root.BuildStringLiteralNode(string(walkerNode.Children[1].Token.Lit))
indexEvaluable := root.BuildStringLiteralNode(tokenLit(walkerNode.Children[1]))
indexEvaluables = append([]IEvaluable{indexEvaluable}, indexEvaluables...)
walkerNode = walkerNode.Children[0]
@ -990,15 +1011,23 @@ type EnvironmentVariableLvalueNode struct {
nameExpression IEvaluable
}
func (root *RootNode) BuildEnvironmentVariableLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEnvironmentVariable)
func (root *RootNode) BuildEnvironmentVariableLvalueNode(astNode *asts.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeEnvironmentVariable))
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
nameExpression, err := root.BuildEvaluableNode(astNode.Children[0])
if err != nil {
return nil, err
child := astNode.Children[0]
var nameExpression IEvaluable
// ENV.FOO: child is non_sigil_name; BuildEvaluableNode doesn't handle bare identifiers
if string(child.Type) == "non_sigil_name" {
sval := tokenLit(child)
nameExpression = root.BuildStringLiteralNode(sval)
} else {
var err error
nameExpression, err = root.BuildEvaluableNode(child)
if err != nil {
return nil, err
}
}
return NewEnvironmentVariableLvalueNode(nameExpression), nil
}

View file

@ -7,11 +7,11 @@ import (
"fmt"
"os"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/output"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/miller/v6/pkg/types"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// Example ASTs:
@ -158,8 +158,8 @@ type PrintStatementNode struct {
outputHandlerManager output.OutputHandlerManager // for file/pipe targets
}
func (root *RootNode) BuildPrintStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypePrintStatement)
func (root *RootNode) BuildPrintStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypePrintStatement))
return root.buildPrintxStatementNode(
astNode,
os.Stdout,
@ -167,8 +167,8 @@ func (root *RootNode) BuildPrintStatementNode(astNode *dsl.ASTNode) (IExecutable
)
}
func (root *RootNode) BuildPrintnStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypePrintnStatement)
func (root *RootNode) BuildPrintnStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypePrintnStatement))
return root.buildPrintxStatementNode(
astNode,
os.Stdout,
@ -176,8 +176,8 @@ func (root *RootNode) BuildPrintnStatementNode(astNode *dsl.ASTNode) (IExecutabl
)
}
func (root *RootNode) BuildEprintStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEprintStatement)
func (root *RootNode) BuildEprintStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeEprintStatement))
return root.buildPrintxStatementNode(
astNode,
os.Stderr,
@ -185,8 +185,8 @@ func (root *RootNode) BuildEprintStatementNode(astNode *dsl.ASTNode) (IExecutabl
)
}
func (root *RootNode) BuildEprintnStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEprintnStatement)
func (root *RootNode) BuildEprintnStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeEprintnStatement))
return root.buildPrintxStatementNode(
astNode,
os.Stderr,
@ -197,25 +197,56 @@ func (root *RootNode) BuildEprintnStatementNode(astNode *dsl.ASTNode) (IExecutab
// Common code for building print/eprint/printn/eprintn nodes
func (root *RootNode) buildPrintxStatementNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
defaultOutputStream *os.File,
terminator string,
) (IExecutable, error) {
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
expressionsNode := astNode.Children[0]
redirectorNode := astNode.Children[1]
// Normalize PGPG AST to 2-child layout (expressions, redirector).
// PGPG produces: 0 children (print), 1 child (print s or print > file), 2+ children (print s,t or print > file, s).
// With "print Redirector comma FcnArgs", children are [Redirector, FcnArgs].
// With "print FcnArgs" (with_adopted_grandchildren), children are the Rvalues directly.
var expressionsNode, redirectorNode *asts.ASTNode
isRedirector := func(n *asts.ASTNode) bool {
return n.Type == asts.NodeType(NodeTypeRedirectWrite) ||
n.Type == asts.NodeType(NodeTypeRedirectAppend) ||
n.Type == asts.NodeType(NodeTypeRedirectPipe)
}
switch len(astNode.Children) {
case 0:
expressionsNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
case 1:
if isRedirector(astNode.Children[0]) {
expressionsNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
redirectorNode = astNode.Children[0]
} else {
expressionsNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), astNode.Children)
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
}
default:
if isRedirector(astNode.Children[0]) {
// print Redirector comma FcnArgs -> [Redirector, FcnArgs]
expressionsNode = astNode.Children[1]
redirectorNode = astNode.Children[0]
} else {
// print FcnArgs with_adopted_grandchildren -> [Rvalue, Rvalue, ...]
expressionsNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeFcnArgs), astNode.Children)
redirectorNode = asts.NewASTNode(nil, asts.NodeType(NodeTypeNoOp), nil)
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Things to be printed, e.g. $a and $b in 'print > "foo.dat", $a, $b'.
var expressionEvaluables []IEvaluable = nil
if expressionsNode.Type == dsl.NodeTypeNoOp {
if expressionsNode.Type == asts.NodeType(NodeTypeNoOp) {
// Just 'print' without 'print $something'
expressionEvaluables = make([]IEvaluable, 1)
expressionEvaluable := root.BuildStringLiteralNode("")
expressionEvaluables[0] = expressionEvaluable
} else if expressionsNode.Type == dsl.NodeTypeFunctionCallsite {
} else if expressionsNode.Type == asts.NodeType(NodeTypeFunctionCallsite) ||
expressionsNode.Type == asts.NodeType(NodeTypeFcnArgs) {
expressionEvaluables = make([]IEvaluable, len(expressionsNode.Children))
for i, childNode := range expressionsNode.Children {
expressionEvaluable, err := root.BuildEvaluableNode(childNode)
@ -239,7 +270,7 @@ func (root *RootNode) buildPrintxStatementNode(
outputHandlerManager: nil,
}
if redirectorNode.Type == dsl.NodeTypeNoOp {
if redirectorNode.Type == asts.NodeType(NodeTypeNoOp) {
// No > >> or | was provided.
if defaultOutputStream == os.Stdout {
retval.printToRedirectFunc = retval.printToStdout
@ -255,23 +286,28 @@ func (root *RootNode) buildPrintxStatementNode(
redirectorTargetNode := redirectorNode.Children[0]
var err error
if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStdout {
if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetStdout) {
retval.printToRedirectFunc = retval.printToStdout
} else if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStderr {
} else if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetStderr) {
retval.printToRedirectFunc = retval.printToStderr
} else {
retval.printToRedirectFunc = retval.printToFileOrPipe
retval.redirectorTargetEvaluable, err = root.BuildEvaluableNode(redirectorTargetNode)
// RedirectTargetRvalue wraps Rvalue; unwrap to build the expression
targetNode := redirectorTargetNode
if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetRvalue) &&
redirectorTargetNode.Children != nil && len(redirectorTargetNode.Children) > 0 {
targetNode = redirectorTargetNode.Children[0]
}
retval.redirectorTargetEvaluable, err = root.BuildEvaluableNode(targetNode)
if err != nil {
return nil, err
}
if redirectorNode.Type == dsl.NodeTypeRedirectWrite {
if redirectorNode.Type == asts.NodeType(NodeTypeRedirectWrite) {
retval.outputHandlerManager = output.NewFileWritetHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectAppend {
} else if redirectorNode.Type == asts.NodeType(NodeTypeRedirectAppend) {
retval.outputHandlerManager = output.NewFileAppendHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectPipe {
} else if redirectorNode.Type == asts.NodeType(NodeTypeRedirectPipe) {
retval.outputHandlerManager = output.NewPipeWriteHandlerManager(root.recordWriterOptions)
} else {
return nil, fmt.Errorf("unhandled redirector node type %s", string(redirectorNode.Type))

View file

@ -10,12 +10,13 @@ import (
"strings"
"github.com/johnkerl/miller/v6/pkg/cli"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/output"
"github.com/johnkerl/miller/v6/pkg/parsing/lexer"
"github.com/johnkerl/miller/v6/pkg/parsing/parser"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// NewEmptyRoot sets up an empty CST, before ingesting any DSL strings. For
@ -59,7 +60,7 @@ func (root *RootNode) WithStrictMode(strictMode bool) *RootNode {
// ASTBuildVisitorFunc is a callback, used by RootNode's Build method, which
// CST-builder callsites can use to visit parse-to-AST result of multi-string
// DSL inputs. Nominal use: mlr put -v, mlr put -d, etc.
type ASTBuildVisitorFunc func(dslString string, astNode *dsl.AST)
type ASTBuildVisitorFunc func(dslString string, astNode *asts.AST)
// Used by DSL -> AST -> CST callsites including mlr put, mlr filter, and mlr
// repl. The RootNode must be separately instantiated (e.g. NewEmptyRoot())
@ -102,7 +103,7 @@ func (root *RootNode) Build(
return hadWarnings, err
}
func buildASTFromStringWithMessage(dslString string) (*dsl.AST, error) {
func buildASTFromStringWithMessage(dslString string) (*asts.AST, error) {
astRootNode, err := buildASTFromString(dslString)
if err != nil {
// Leave this out until we get better control over the error-messaging.
@ -113,13 +114,13 @@ func buildASTFromStringWithMessage(dslString string) (*dsl.AST, error) {
return astRootNode, nil
}
func buildASTFromString(dslString string) (*dsl.AST, error) {
func buildASTFromString(dslString string) (*asts.AST, error) {
// For non-Windows, already stripped by the shell; helpful here for Windows.
if strings.HasPrefix(dslString, "'") && strings.HasSuffix(dslString, "'") {
dslString = dslString[1 : len(dslString)-1]
}
// The comment-stripping lex expression in Miller's GOCC grammar matches from '#' to '\n' ... in
// The comment-stripping lex expression in Miller's grammar matches from '#' to '\n' ... in
// the case where there is a '#', then comment text, then end-of-string without any final
// newline, the comment text does _not_ get stripped out and is a parse error.
// It's simplest to ensure, here, that DSL-expression strings have a final newline.
@ -128,13 +129,8 @@ func buildASTFromString(dslString string) (*dsl.AST, error) {
}
theLexer := lexer.NewLexer([]byte(dslString))
theParser := parser.NewParser()
interfaceAST, err := theParser.Parse(theLexer)
if err != nil {
return nil, err
}
astRootNode := interfaceAST.(*dsl.AST)
return astRootNode, nil
theParser := parser.NewMlrParser()
return theParser.Parse(theLexer, "")
}
// If the user has multiple put -f / put -e pieces, we can AST-parse each
@ -142,7 +138,7 @@ func buildASTFromString(dslString string) (*dsl.AST, error) {
// until after they're all ingested -- e.g. first piece calls a function which
// the second defines, or mutual recursion across pieces, etc.
func (root *RootNode) IngestAST(
ast *dsl.AST,
ast *asts.AST,
// False for non-REPL use. Also false for bulk-load REPL use. True for
// interactive REPL statements which are intended to be executed once
// (immediately) but not retained.
@ -231,37 +227,33 @@ func (root *RootNode) Resolve() error {
// * string literal " "
// * string literal "TAB"
func (root *RootNode) regexProtectPrePass(ast *dsl.AST) {
func (root *RootNode) regexProtectPrePass(ast *asts.AST) {
root.regexProtectPrePassAux(ast.RootNode)
}
func (root *RootNode) regexProtectPrePassAux(astNode *dsl.ASTNode) {
func (root *RootNode) regexProtectPrePassAux(astNode *asts.ASTNode) {
if len(astNode.Children) == 0 {
return
}
isCallsiteOfInterest := false
if astNode.Type == dsl.NodeTypeOperator {
if astNode.Token != nil {
nodeName := string(astNode.Token.Lit)
if nodeName == "=~" || nodeName == "!=~" {
isCallsiteOfInterest = true
}
if astNode.Type == asts.NodeType(NodeTypeOperator) {
nodeName := tokenLit(astNode)
if nodeName == "=~" || nodeName == "!=~" {
isCallsiteOfInterest = true
}
} else if astNode.Type == dsl.NodeTypeFunctionCallsite {
if astNode.Token != nil {
nodeName := string(astNode.Token.Lit)
if nodeName == "sub" || nodeName == "gsub" || nodeName == "regextract" || nodeName == "regextract_or_else" {
isCallsiteOfInterest = true
}
} else if astNode.Type == asts.NodeType(NodeTypeFunctionCallsite) {
nodeName := tokenLit(astNode)
if nodeName == "sub" || nodeName == "gsub" || nodeName == "regextract" || nodeName == "regextract_or_else" {
isCallsiteOfInterest = true
}
}
for i, astChild := range astNode.Children {
if isCallsiteOfInterest && i == 1 {
if astChild.Type == dsl.NodeTypeStringLiteral {
astChild.Type = dsl.NodeTypeRegex
if astChild.Type == asts.NodeType(NodeTypeStringLiteral) {
astChild.Type = asts.NodeType(NodeTypeRegex)
}
}
root.regexProtectPrePassAux(astChild)
@ -273,9 +265,9 @@ func (root *RootNode) regexProtectPrePassAux(astNode *dsl.ASTNode) {
// functions may be called before they are defined, so a follow-up pass will
// need to resolve those callsites.
func (root *RootNode) buildMainPass(ast *dsl.AST, isReplImmediate bool) error {
func (root *RootNode) buildMainPass(ast *asts.AST, isReplImmediate bool) error {
if ast.RootNode.Type != dsl.NodeTypeStatementBlock {
if ast.RootNode.Type != asts.NodeType(NodeTypeStatementBlock) {
return fmt.Errorf("at CST root build: non-statement-block AST root node unhandled")
}
astChildren := ast.RootNode.Children
@ -304,25 +296,25 @@ func (root *RootNode) buildMainPass(ast *dsl.AST, isReplImmediate bool) error {
for _, astChild := range astChildren {
if astChild.Type == dsl.NodeTypeNamedFunctionDefinition {
if astChild.Type == asts.NodeType(NodeTypeNamedFunctionDefinition) {
err := root.BuildAndInstallUDF(astChild)
if err != nil {
return err
}
} else if astChild.Type == dsl.NodeTypeSubroutineDefinition {
} else if astChild.Type == asts.NodeType(NodeTypeSubroutineDefinition) {
err := root.BuildAndInstallUDS(astChild)
if err != nil {
return err
}
} else if astChild.Type == dsl.NodeTypeBeginBlock || astChild.Type == dsl.NodeTypeEndBlock {
} else if astChild.Type == asts.NodeType(NodeTypeBeginBlock) || astChild.Type == asts.NodeType(NodeTypeEndBlock) {
statementBlockNode, err := root.BuildStatementBlockNodeFromBeginOrEnd(astChild)
if err != nil {
return err
}
if astChild.Type == dsl.NodeTypeBeginBlock {
if astChild.Type == asts.NodeType(NodeTypeBeginBlock) {
root.beginBlocks = append(root.beginBlocks, statementBlockNode)
} else {
root.endBlocks = append(root.endBlocks, statementBlockNode)

View file

@ -6,24 +6,30 @@ package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
func (root *RootNode) BuildStatementNode(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) (IExecutable, error) {
var statement IExecutable = nil
var err error
switch astNode.Type {
case dsl.NodeTypeAssignment:
case asts.NodeType(NodeTypeAssignment):
statement, err = root.BuildAssignmentNode(astNode)
if err != nil {
return nil, err
}
case dsl.NodeTypeUnset:
case asts.NodeType(NodeTypeCompoundAssignment):
statement, err = root.BuildCompoundAssignmentNode(astNode)
if err != nil {
return nil, err
}
case asts.NodeType(NodeTypeUnset):
statement, err = root.BuildUnsetNode(astNode)
if err != nil {
return nil, err
@ -32,71 +38,71 @@ func (root *RootNode) BuildStatementNode(
// E.g. 'NR > 10' without if or '{...}' body. For put, these are no-ops
// except side-effects (like regex-captures); for filter, they set the
// filter condition only if they're the last statement in the main block.
case dsl.NodeTypeBareBoolean:
case asts.NodeType(NodeTypeBareBoolean):
return root.BuildBareBooleanStatementNode(astNode)
// E.g. 'filter NR > 10'.
case dsl.NodeTypeFilterStatement:
case asts.NodeType(NodeTypeFilterStatement):
return root.BuildFilterStatementNode(astNode)
case dsl.NodeTypePrintStatement:
case asts.NodeType(NodeTypePrintStatement):
return root.BuildPrintStatementNode(astNode)
case dsl.NodeTypePrintnStatement:
case asts.NodeType(NodeTypePrintnStatement):
return root.BuildPrintnStatementNode(astNode)
case dsl.NodeTypeEprintStatement:
case asts.NodeType(NodeTypeEprintStatement):
return root.BuildEprintStatementNode(astNode)
case dsl.NodeTypeEprintnStatement:
case asts.NodeType(NodeTypeEprintnStatement):
return root.BuildEprintnStatementNode(astNode)
case dsl.NodeTypeDumpStatement:
case asts.NodeType(NodeTypeDumpStatement):
return root.BuildDumpStatementNode(astNode)
case dsl.NodeTypeEdumpStatement:
case asts.NodeType(NodeTypeEdumpStatement):
return root.BuildEdumpStatementNode(astNode)
case dsl.NodeTypeTeeStatement:
case asts.NodeType(NodeTypeTeeStatement):
return root.BuildTeeStatementNode(astNode)
case dsl.NodeTypeEmit1Statement:
case asts.NodeType(NodeTypeEmit1Statement):
return root.BuildEmit1StatementNode(astNode)
case dsl.NodeTypeEmitStatement:
case asts.NodeType(NodeTypeEmitStatement):
return root.BuildEmitStatementNode(astNode)
case dsl.NodeTypeEmitFStatement:
case asts.NodeType(NodeTypeEmitFStatement):
return root.BuildEmitFStatementNode(astNode)
case dsl.NodeTypeEmitPStatement:
case asts.NodeType(NodeTypeEmitPStatement):
return root.BuildEmitPStatementNode(astNode)
case dsl.NodeTypeBeginBlock:
case asts.NodeType(NodeTypeBeginBlock):
return nil, fmt.Errorf("begin blocks may only be declared at top level")
case dsl.NodeTypeEndBlock:
case asts.NodeType(NodeTypeEndBlock):
return nil, fmt.Errorf("end blocks may only be declared at top level")
case dsl.NodeTypeIfChain:
case asts.NodeType(NodeTypeIfChain):
return root.BuildIfChainNode(astNode)
case dsl.NodeTypeCondBlock:
case asts.NodeType(NodeTypeCondBlock):
return root.BuildCondBlockNode(astNode)
case dsl.NodeTypeWhileLoop:
case asts.NodeType(NodeTypeWhileLoop):
return root.BuildWhileLoopNode(astNode)
case dsl.NodeTypeDoWhileLoop:
case asts.NodeType(NodeTypeDoWhileLoop):
return root.BuildDoWhileLoopNode(astNode)
case dsl.NodeTypeForLoopOneVariable:
case asts.NodeType(NodeTypeForLoopOneVariable):
return root.BuildForLoopOneVariableNode(astNode)
case dsl.NodeTypeForLoopTwoVariable:
case asts.NodeType(NodeTypeForLoopTwoVariable):
return root.BuildForLoopTwoVariableNode(astNode)
case dsl.NodeTypeForLoopMultivariable:
case asts.NodeType(NodeTypeForLoopMultivariable):
return root.BuildForLoopMultivariableNode(astNode)
case dsl.NodeTypeTripleForLoop:
case asts.NodeType(NodeTypeTripleForLoop):
return root.BuildTripleForLoopNode(astNode)
case dsl.NodeTypeNamedFunctionDefinition:
case asts.NodeType(NodeTypeNamedFunctionDefinition):
return nil, fmt.Errorf("functions may only be declared at top level")
case dsl.NodeTypeSubroutineDefinition:
case asts.NodeType(NodeTypeSubroutineDefinition):
return nil, fmt.Errorf("subroutines may only be declared at top level")
case dsl.NodeTypeSubroutineCallsite:
case asts.NodeType(NodeTypeSubroutineCallsite):
return root.BuildSubroutineCallsiteNode(astNode)
case dsl.NodeTypeBreak:
case asts.NodeType(NodeTypeBreakStatement):
return root.BuildBreakNode(astNode)
case dsl.NodeTypeContinue:
case asts.NodeType(NodeTypeContinueStatement):
return root.BuildContinueNode(astNode)
case dsl.NodeTypeReturn:
case asts.NodeType(NodeTypeReturnStatement):
return root.BuildReturnNode(astNode)
default:

View file

@ -7,8 +7,8 @@
package cst
import (
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// Subroutine lookup:
@ -22,15 +22,15 @@ import (
// o On a next pass, we will walk that list resolving against all encountered
// UDS definitions. (It will be an error then if it's still unresolvable.)
func (root *RootNode) BuildSubroutineCallsiteNode(astNode *dsl.ASTNode) (IExecutable, error) {
func (root *RootNode) BuildSubroutineCallsiteNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(
astNode.Type != dsl.NodeTypeSubroutineCallsite &&
astNode.Type != dsl.NodeTypeOperator,
astNode.Type != asts.NodeType(NodeTypeSubroutineCallsite) &&
astNode.Type != asts.NodeType(NodeTypeOperator),
)
lib.InternalCodingErrorIf(astNode.Token == nil)
lib.InternalCodingErrorIf(astNode.Children == nil)
subroutineName := string(astNode.Token.Lit)
subroutineName := tokenLit(astNode)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Look for a user-defined subroutine with the given name.

View file

@ -5,12 +5,12 @@ package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/output"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/miller/v6/pkg/types"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// Examples:
@ -64,16 +64,17 @@ type TeeStatementNode struct {
outputHandlerManager output.OutputHandlerManager // for file/pipe targets
}
func (root *RootNode) BuildTeeStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeTeeStatement)
func (root *RootNode) BuildTeeStatementNode(astNode *asts.ASTNode) (IExecutable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeTeeStatement))
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
expressionNode := astNode.Children[0]
redirectorNode := astNode.Children[1]
// PGPG: kw_tee Redirector comma FullSrec -> children [1,3] = [Redirector, FullSrec]
redirectorNode := astNode.Children[0]
expressionNode := astNode.Children[1]
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Expression to be teed, which is $*.
lib.InternalCodingErrorIf(expressionNode.Type != dsl.NodeTypeFullSrec)
lib.InternalCodingErrorIf(expressionNode.Type != asts.NodeType(NodeTypeFullSrec))
expressionEvaluable := root.BuildFullSrecRvalueNode()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -92,27 +93,31 @@ func (root *RootNode) BuildTeeStatementNode(astNode *dsl.ASTNode) (IExecutable,
redirectorTargetNode := redirectorNode.Children[0]
var err error
if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStdout {
if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetStdout) {
retval.teeToRedirectFunc = retval.teeToFileOrPipe
retval.outputHandlerManager = output.NewStdoutWriteHandlerManager(root.recordWriterOptions)
retval.redirectorTargetEvaluable = root.BuildStringLiteralNode("(stdout)")
} else if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStderr {
} else if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetStderr) {
retval.teeToRedirectFunc = retval.teeToFileOrPipe
retval.outputHandlerManager = output.NewStderrWriteHandlerManager(root.recordWriterOptions)
retval.redirectorTargetEvaluable = root.BuildStringLiteralNode("(stderr)")
} else {
retval.teeToRedirectFunc = retval.teeToFileOrPipe
retval.redirectorTargetEvaluable, err = root.BuildEvaluableNode(redirectorTargetNode)
targetNode := redirectorTargetNode
if redirectorTargetNode.Type == asts.NodeType(NodeTypeRedirectTargetRvalue) &&
redirectorTargetNode.Children != nil && len(redirectorTargetNode.Children) > 0 {
targetNode = redirectorTargetNode.Children[0]
}
retval.redirectorTargetEvaluable, err = root.BuildEvaluableNode(targetNode)
if err != nil {
return nil, err
}
if redirectorNode.Type == dsl.NodeTypeRedirectWrite {
if redirectorNode.Type == asts.NodeType(NodeTypeRedirectWrite) {
retval.outputHandlerManager = output.NewFileWritetHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectAppend {
} else if redirectorNode.Type == asts.NodeType(NodeTypeRedirectAppend) {
retval.outputHandlerManager = output.NewFileAppendHandlerManager(root.recordWriterOptions)
} else if redirectorNode.Type == dsl.NodeTypeRedirectPipe {
} else if redirectorNode.Type == asts.NodeType(NodeTypeRedirectPipe) {
retval.outputHandlerManager = output.NewPipeWriteHandlerManager(root.recordWriterOptions)
} else {
return nil, fmt.Errorf("unhandled redirector node type %s", string(redirectorNode.Type))

View file

@ -4,10 +4,10 @@ package cst
import (
"github.com/johnkerl/miller/v6/pkg/cli"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/output"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// DSLInstanceType is for minor differences in DSL handling between mlr put,
@ -50,7 +50,7 @@ type RootNode struct {
// Many functions have this signature. This type-alias is for function-name
// lookup tables.
type NodeBuilder func(astNode *dsl.ASTNode) (IEvaluable, error)
type NodeBuilder func(astNode *asts.ASTNode) (IEvaluable, error)
// This is for all statements and statement blocks within the CST.
type IExecutable interface {

View file

@ -6,11 +6,11 @@ import (
"fmt"
"os"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/miller/v6/pkg/types"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
type UDF struct {
@ -391,12 +391,12 @@ func (mgr *UDFManager) LookUpDisregardingArity(functionName string) *UDF {
// * DirectFieldValue "x"
// BuildAndInstallUDF is for named UDFs, like `func f(a, b) { return b - a}'.
func (root *RootNode) BuildAndInstallUDF(astNode *dsl.ASTNode) error {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeNamedFunctionDefinition)
func (root *RootNode) BuildAndInstallUDF(astNode *asts.ASTNode) error {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeNamedFunctionDefinition))
lib.InternalCodingErrorIf(astNode.Children == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 2 && len(astNode.Children) != 3)
functionName := string(astNode.Token.Lit)
functionName := tokenLit(astNode)
if BuiltinFunctionManagerInstance.LookUp(functionName) != nil {
return fmt.Errorf(
@ -438,8 +438,8 @@ type UnnamedUDFNode struct {
udfAsMlrval *mlrval.Mlrval
}
func (root *RootNode) BuildUnnamedUDFNode(astNode *dsl.ASTNode) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeUnnamedFunctionDefinition)
func (root *RootNode) BuildUnnamedUDFNode(astNode *asts.ASTNode) (IEvaluable, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeUnnamedFunctionDefinition))
name := genFunctionLiteralName()
@ -459,28 +459,82 @@ func (node *UnnamedUDFNode) Evaluate(state *runtime.State) *mlrval.Mlrval {
return node.udfAsMlrval
}
// typedDeclNodeTypeToName maps PGPG type-keyword node types to TypeNameToMask names.
func typedDeclNodeTypeToName(nodeType string) string {
switch nodeType {
case "kw_int", "int":
return "int"
case "kw_float", "float":
return "float"
case "kw_num":
return "num"
case "kw_bool", "bool":
return "bool"
case "kw_str", "str":
return "str"
case "kw_arr", "arr":
return "arr"
case "kw_map", "map":
return "map"
case "kw_var", "var":
return "var"
case "kw_funct", "funct":
return "funct"
case "Typedecl":
return "any"
}
return "any"
}
// flattenParameterList recurses through nested ParameterList nodes and returns
// a flat list of Parameter or LocalVariable nodes. PGPG produces (params (params (Parameter) (Parameter))).
func flattenParameterList(nodes []*asts.ASTNode) []*asts.ASTNode {
var out []*asts.ASTNode
for _, n := range nodes {
if n == nil {
continue
}
if string(n.Type) == NodeTypeParameterList || n.Type == asts.NodeType(NodeTypeParameterList) {
if n.Children != nil {
out = append(out, flattenParameterList(n.Children)...)
}
continue
}
out = append(out, n)
}
return out
}
// BuildUDF is for named UDFs, like `func f(a, b) { return b - a}', or,
// unnamed UDFs like `func (a, b) { return b - a }'.
func (root *RootNode) BuildUDF(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
functionName string,
isFunctionLiteral bool,
) (*UDF, error) {
lib.InternalCodingErrorIf(
(astNode.Type != dsl.NodeTypeNamedFunctionDefinition) &&
(astNode.Type != dsl.NodeTypeUnnamedFunctionDefinition))
(astNode.Type != asts.NodeType(NodeTypeNamedFunctionDefinition)) &&
(astNode.Type != asts.NodeType(NodeTypeUnnamedFunctionDefinition)))
lib.InternalCodingErrorIf(astNode.Children == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 2 && len(astNode.Children) != 3)
parameterListASTNode := astNode.Children[0]
functionBodyASTNode := astNode.Children[1]
var functionBodyASTNode *asts.ASTNode
returnValueTypeName := "any"
if len(astNode.Children) == 3 {
typeNode := astNode.Children[2]
lib.InternalCodingErrorIf(typeNode.Type != dsl.NodeTypeTypedecl)
returnValueTypeName = string(typeNode.Token.Lit)
// PGPG: children [2, 4, 5] = [FuncParams, Typedecl, StatementBlockInBraces]
typeNode := astNode.Children[1]
functionBodyASTNode = astNode.Children[2]
returnValueTypeName = tokenLit(typeNode)
if returnValueTypeName == "" && typeNode.Children != nil && len(typeNode.Children) > 0 {
returnValueTypeName = tokenLit(typeNode.Children[0])
}
if returnValueTypeName == "" {
returnValueTypeName = typedDeclNodeTypeToName(string(typeNode.Type))
}
} else {
functionBodyASTNode = astNode.Children[1]
}
typeGatedReturnValue, err := types.NewTypeGatedMlrvalName(
"function return value",
@ -490,24 +544,60 @@ func (root *RootNode) BuildUDF(
return nil, err
}
lib.InternalCodingErrorIf(parameterListASTNode.Type != dsl.NodeTypeParameterList)
// PGPG may use different type strings; accept ParameterList by structure
if string(parameterListASTNode.Type) != NodeTypeParameterList && parameterListASTNode.Type != asts.NodeType(NodeTypeParameterList) {
lib.InternalCodingErrorWithMessageIf(true, "expected ParameterList node")
}
lib.InternalCodingErrorIf(parameterListASTNode.Children == nil)
arity := len(parameterListASTNode.Children)
typeGatedParameterNames := make([]*types.TypeGatedMlrvalName, arity)
for i, parameterASTNode := range parameterListASTNode.Children {
lib.InternalCodingErrorIf(parameterASTNode.Type != dsl.NodeTypeParameter)
lib.InternalCodingErrorIf(parameterASTNode.Children == nil)
lib.InternalCodingErrorIf(len(parameterASTNode.Children) != 1)
typeGatedParameterNameASTNode := parameterASTNode.Children[0]
lib.InternalCodingErrorIf(typeGatedParameterNameASTNode.Type != dsl.NodeTypeParameterName)
variableName := string(typeGatedParameterNameASTNode.Token.Lit)
typeName := "any"
if typeGatedParameterNameASTNode.Children != nil { // typed parameter like 'num x'
lib.InternalCodingErrorIf(len(typeGatedParameterNameASTNode.Children) != 1)
typeNode := typeGatedParameterNameASTNode.Children[0]
lib.InternalCodingErrorIf(typeNode.Type != dsl.NodeTypeTypedecl)
typeName = string(typeNode.Token.Lit)
// PGPG produces nested ParameterList (params contains params contains Parameter, Parameter).
// Flatten to a list of Parameter or LocalVariable nodes.
flatParams := flattenParameterList(parameterListASTNode.Children)
arity := len(flatParams)
typeGatedParameterNames := make([]*types.TypeGatedMlrvalName, arity)
for i, parameterASTNode := range flatParams {
// PGPG: Parameter has one child (LocalVariable), or two (Typedecl, LocalVariable) for typed params.
var variableName string
var typeName string = "any"
if parameterASTNode.Children != nil && len(parameterASTNode.Children) == 2 {
// Typedecl LocalVariable -> [Typedecl, LocalVariable]
typeNode := parameterASTNode.Children[0]
nameNode := parameterASTNode.Children[1]
typeName = tokenLit(typeNode)
if typeName == "" && typeNode.Children != nil && len(typeNode.Children) > 0 {
typeName = tokenLit(typeNode.Children[0])
}
if typeName == "" {
// PGPG may produce type keywords (kw_int etc) without token on node; use node type
typeName = typedDeclNodeTypeToName(string(typeNode.Type))
}
if string(nameNode.Type) == NodeTypeLocalVariable || nameNode.Type == asts.NodeType(NodeTypeLocalVariable) {
variableName = tokenLit(nameNode)
}
} else if parameterASTNode.Children != nil && len(parameterASTNode.Children) == 1 {
typeGatedParameterNameASTNode := parameterASTNode.Children[0]
if string(typeGatedParameterNameASTNode.Type) != NodeTypeLocalVariable &&
typeGatedParameterNameASTNode.Type != asts.NodeType(NodeTypeLocalVariable) {
lib.InternalCodingErrorWithMessageIf(true, "expected LocalVariable as parameter name")
}
variableName = tokenLit(typeGatedParameterNameASTNode)
} else if parameterASTNode.Children == nil || len(parameterASTNode.Children) == 0 {
// Direct LocalVariable (e.g. from with_prepended_children flattening)
if string(parameterASTNode.Type) != NodeTypeLocalVariable &&
parameterASTNode.Type != asts.NodeType(NodeTypeLocalVariable) {
lib.InternalCodingErrorWithMessageIf(true, "expected Parameter or LocalVariable")
}
variableName = tokenLit(parameterASTNode)
} else {
// PGPG may produce Parameter with multiple children; try first child, then node itself
typeGatedParameterNameASTNode := parameterASTNode.Children[0]
variableName = tokenLit(typeGatedParameterNameASTNode)
if variableName == "" {
variableName = tokenLit(parameterASTNode)
}
if variableName == "" {
lib.InternalCodingErrorWithMessageIf(true, "expected Parameter node with one child")
}
}
typeGatedParameterName, err := types.NewTypeGatedMlrvalName(
variableName,

View file

@ -5,11 +5,11 @@ package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/miller/v6/pkg/types"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
type UDS struct {
@ -228,12 +228,12 @@ func (mgr *UDSManager) ExistsByName(name string) bool {
// * SubroutineCallsite "f"
// * DirectFieldValue "x"
func (root *RootNode) BuildAndInstallUDS(astNode *dsl.ASTNode) error {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeSubroutineDefinition)
func (root *RootNode) BuildAndInstallUDS(astNode *asts.ASTNode) error {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeSubroutineDefinition))
lib.InternalCodingErrorIf(astNode.Children == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 2 && len(astNode.Children) != 3)
subroutineName := string(astNode.Token.Lit)
subroutineName := tokenLit(astNode)
if !root.allowUDFUDSRedefinitions {
if root.udsManager.ExistsByName(subroutineName) {
@ -247,24 +247,34 @@ func (root *RootNode) BuildAndInstallUDS(astNode *dsl.ASTNode) error {
parameterListASTNode := astNode.Children[0]
subroutineBodyASTNode := astNode.Children[1]
lib.InternalCodingErrorIf(parameterListASTNode.Type != dsl.NodeTypeParameterList)
lib.InternalCodingErrorIf(parameterListASTNode.Type != asts.NodeType(NodeTypeParameterList))
lib.InternalCodingErrorIf(parameterListASTNode.Children == nil)
arity := len(parameterListASTNode.Children)
flatParams := flattenParameterList(parameterListASTNode.Children)
arity := len(flatParams)
typeGatedParameterNames := make([]*types.TypeGatedMlrvalName, arity)
for i, parameterASTNode := range parameterListASTNode.Children {
lib.InternalCodingErrorIf(parameterASTNode.Type != dsl.NodeTypeParameter)
lib.InternalCodingErrorIf(parameterASTNode.Children == nil)
lib.InternalCodingErrorIf(len(parameterASTNode.Children) != 1)
typeGatedParameterNameASTNode := parameterASTNode.Children[0]
lib.InternalCodingErrorIf(typeGatedParameterNameASTNode.Type != dsl.NodeTypeParameterName)
variableName := string(typeGatedParameterNameASTNode.Token.Lit)
typeName := "any"
if typeGatedParameterNameASTNode.Children != nil { // typed parameter like 'num x'
lib.InternalCodingErrorIf(len(typeGatedParameterNameASTNode.Children) != 1)
typeNode := typeGatedParameterNameASTNode.Children[0]
lib.InternalCodingErrorIf(typeNode.Type != dsl.NodeTypeTypedecl)
typeName = string(typeNode.Token.Lit)
for i, parameterASTNode := range flatParams {
var variableName string
var typeName string = "any"
if parameterASTNode.Children != nil && len(parameterASTNode.Children) == 2 {
// Typedecl LocalVariable -> [Typedecl, LocalVariable]
typeNode := parameterASTNode.Children[0]
nameNode := parameterASTNode.Children[1]
typeName = tokenLit(typeNode)
if typeName == "" && typeNode.Children != nil && len(typeNode.Children) > 0 {
typeName = tokenLit(typeNode.Children[0])
}
if typeName == "" {
typeName = typedDeclNodeTypeToName(string(typeNode.Type))
}
if string(nameNode.Type) == NodeTypeLocalVariable || nameNode.Type == asts.NodeType(NodeTypeLocalVariable) {
variableName = tokenLit(nameNode)
}
} else if parameterASTNode.Children != nil && len(parameterASTNode.Children) == 1 {
typeGatedParameterNameASTNode := parameterASTNode.Children[0]
lib.InternalCodingErrorIf(typeGatedParameterNameASTNode.Type != asts.NodeType(NodeTypeLocalVariable))
variableName = tokenLit(typeGatedParameterNameASTNode)
} else {
lib.InternalCodingErrorWithMessageIf(true, "expected Parameter with 1 or 2 children")
}
typeGatedParameterName, err := types.NewTypeGatedMlrvalName(
variableName,
@ -273,7 +283,6 @@ func (root *RootNode) BuildAndInstallUDS(astNode *dsl.ASTNode) error {
if err != nil {
return err
}
typeGatedParameterNames[i] = typeGatedParameterName
}

View file

@ -7,12 +7,13 @@ package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
func ValidateAST(
ast *dsl.AST,
ast *asts.AST,
dslInstanceType DSLInstanceType, // mlr put, mlr filter, mlr repl
) error {
atTopLevel := true
@ -54,7 +55,7 @@ func ValidateAST(
}
func validateASTAux(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
dslInstanceType DSLInstanceType, // mlr put, mlr filter, mlr repl
atTopLevel bool,
inLoop bool,
@ -73,7 +74,7 @@ func validateASTAux(
nextLevelIsAssignmentLHS := isAssignmentLHS
nextLevelIsUnset := isUnset
if astNode.Type == dsl.NodeTypeFilterStatement {
if astNode.Type == asts.NodeType(NodeTypeFilterStatement) {
if dslInstanceType == DSLInstanceTypeFilter {
return fmt.Errorf(
`filter expressions must not also contain the "filter" keyword`,
@ -82,42 +83,42 @@ func validateASTAux(
}
// Check: begin/end/func/subr must be at top-level
if astNode.Type == dsl.NodeTypeBeginBlock {
if astNode.Type == asts.NodeType(NodeTypeBeginBlock) {
if !atTopLevel {
return fmt.Errorf(
"begin blocks can only be at top level",
)
}
nextLevelInBeginOrEnd = true
} else if astNode.Type == dsl.NodeTypeEndBlock {
} else if astNode.Type == asts.NodeType(NodeTypeEndBlock) {
if !atTopLevel {
return fmt.Errorf(
"end blocks can only be at top level",
)
}
nextLevelInBeginOrEnd = true
} else if astNode.Type == dsl.NodeTypeNamedFunctionDefinition {
} else if astNode.Type == asts.NodeType(NodeTypeNamedFunctionDefinition) {
if !atTopLevel {
return fmt.Errorf(
"func blocks can only be at top level",
)
}
nextLevelInUDF = true
} else if astNode.Type == dsl.NodeTypeUnnamedFunctionDefinition {
} else if astNode.Type == asts.NodeType(NodeTypeUnnamedFunctionDefinition) {
nextLevelInUDF = true
} else if astNode.Type == dsl.NodeTypeSubroutineDefinition {
} else if astNode.Type == asts.NodeType(NodeTypeSubroutineDefinition) {
if !atTopLevel {
return fmt.Errorf(
"subr blocks can only be at top level",
)
}
nextLevelInUDS = true
} else if astNode.Type == dsl.NodeTypeForLoopTwoVariable {
} else if astNode.Type == asts.NodeType(NodeTypeForLoopTwoVariable) {
err := validateForLoopTwoVariableUniqueNames(astNode)
if err != nil {
return err
}
} else if astNode.Type == dsl.NodeTypeForLoopMultivariable {
} else if astNode.Type == asts.NodeType(NodeTypeForLoopMultivariable) {
err := validateForLoopMultivariableUniqueNames(astNode)
if err != nil {
return err
@ -126,9 +127,9 @@ func validateASTAux(
// Check: $-anything cannot be in begin/end
if inBeginOrEnd {
if astNode.Type == dsl.NodeTypeDirectFieldValue ||
astNode.Type == dsl.NodeTypeIndirectFieldValue ||
astNode.Type == dsl.NodeTypeFullSrec {
if astNode.Type == asts.NodeType(NodeTypeDirectFieldValue) ||
astNode.Type == asts.NodeType(NodeTypeIndirectFieldValue) ||
astNode.Type == asts.NodeType(NodeTypeFullSrec) {
return fmt.Errorf(
"begin/end blocks cannot refer to records via $x, $*, etc",
)
@ -137,7 +138,7 @@ func validateASTAux(
// Check: break/continue outside of loop
if !inLoop {
if astNode.Type == dsl.NodeTypeBreak {
if astNode.Type == asts.NodeType(NodeTypeBreakStatement) {
return fmt.Errorf(
"break statements are only valid within for/do/while loops",
)
@ -145,25 +146,25 @@ func validateASTAux(
}
if !inLoop {
if astNode.Type == dsl.NodeTypeContinue {
if astNode.Type == asts.NodeType(NodeTypeContinueStatement) {
return fmt.Errorf(
"break statements are only valid within for/do/while loops",
)
}
}
if astNode.Type == dsl.NodeTypeWhileLoop ||
astNode.Type == dsl.NodeTypeDoWhileLoop ||
astNode.Type == dsl.NodeTypeForLoopOneVariable ||
astNode.Type == dsl.NodeTypeForLoopTwoVariable ||
astNode.Type == dsl.NodeTypeForLoopMultivariable ||
astNode.Type == dsl.NodeTypeTripleForLoop {
if astNode.Type == asts.NodeType(NodeTypeWhileLoop) ||
astNode.Type == asts.NodeType(NodeTypeDoWhileLoop) ||
astNode.Type == asts.NodeType(NodeTypeForLoopOneVariable) ||
astNode.Type == asts.NodeType(NodeTypeForLoopTwoVariable) ||
astNode.Type == asts.NodeType(NodeTypeForLoopMultivariable) ||
astNode.Type == asts.NodeType(NodeTypeTripleForLoop) {
nextLevelInLoop = true
}
// Check: return outside of func/subr
if !inUDF && !inUDS {
if astNode.Type == dsl.NodeTypeReturn {
if astNode.Type == asts.NodeType(NodeTypeReturnStatement) {
return fmt.Errorf(
"return statements are only valid within func/subr blocks",
)
@ -171,7 +172,7 @@ func validateASTAux(
}
// Check: enforce return-value iff in a function; return-void iff in a subroutine
if astNode.Type == dsl.NodeTypeReturn {
if astNode.Type == asts.NodeType(NodeTypeReturnStatement) {
if inUDF {
if len(astNode.Children) != 1 {
return fmt.Errorf(
@ -190,7 +191,7 @@ func validateASTAux(
// Check: prohibit NR etc at LHS; 1+2=3+4; etc
if isAssignmentLHS {
ok := VALID_LHS_NODE_TYPES[astNode.Type]
ok := VALID_LHS_NODE_TYPES[string(astNode.Type)]
if !ok {
return fmt.Errorf(
"%s is not valid on the left-hand side of an assignment",
@ -201,7 +202,7 @@ func validateASTAux(
// Check: prohibit NR etc at LHS; 1+2=3+4; etc
if isUnset {
ok := VALID_LHS_NODE_TYPES[astNode.Type]
ok := VALID_LHS_NODE_TYPES[string(astNode.Type)]
if !ok {
return fmt.Errorf(
"%s is not valid for unset statement",
@ -214,8 +215,8 @@ func validateASTAux(
// Treewalk
for i, astChild := range astNode.Children {
nextLevelIsAssignmentLHS = astNode.Type == dsl.NodeTypeAssignment && i == 0
nextLevelIsUnset = astNode.Type == dsl.NodeTypeUnset
nextLevelIsAssignmentLHS = (astNode.Type == asts.NodeType(NodeTypeAssignment) || astNode.Type == asts.NodeType(NodeTypeCompoundAssignment)) && i == 0
nextLevelIsUnset = astNode.Type == asts.NodeType(NodeTypeUnset)
err := validateASTAux(
astChild,
dslInstanceType,
@ -245,15 +246,15 @@ func validateASTAux(
// * full record "$*"
// * statement block
func validateForLoopTwoVariableUniqueNames(astNode *dsl.ASTNode) error {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeForLoopTwoVariable)
func validateForLoopTwoVariableUniqueNames(astNode *asts.ASTNode) error {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeForLoopTwoVariable))
lib.InternalCodingErrorIf(len(astNode.Children) != 4)
keyVarNode := astNode.Children[0]
valVarNode := astNode.Children[1]
lib.InternalCodingErrorIf(keyVarNode.Type != dsl.NodeTypeLocalVariable)
lib.InternalCodingErrorIf(valVarNode.Type != dsl.NodeTypeLocalVariable)
keyVarName := string(keyVarNode.Token.Lit)
valVarName := string(valVarNode.Token.Lit)
lib.InternalCodingErrorIf(keyVarNode.Type != asts.NodeType(NodeTypeLocalVariable))
lib.InternalCodingErrorIf(valVarNode.Type != asts.NodeType(NodeTypeLocalVariable))
keyVarName := tokenLit(keyVarNode)
valVarName := tokenLit(valVarNode)
if keyVarName == valVarName {
return fmt.Errorf("redefinition of variable %s in the same scope", keyVarName)
}
@ -270,18 +271,20 @@ func validateForLoopTwoVariableUniqueNames(astNode *dsl.ASTNode) error {
// - local variable "a"
// - full record "$*"
// - statement block
func validateForLoopMultivariableUniqueNames(astNode *dsl.ASTNode) error {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeForLoopMultivariable)
func validateForLoopMultivariableUniqueNames(astNode *asts.ASTNode) error {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeForLoopMultivariable))
keyVarsNode := astNode.Children[0]
valVarNode := astNode.Children[1]
lib.InternalCodingErrorIf(keyVarsNode.Type != dsl.NodeTypeParameterList)
lib.InternalCodingErrorIf(valVarNode.Type != dsl.NodeTypeLocalVariable)
// PGPG produces MultiIndex; legacy produced ParameterList. Both have LocalVariable children.
lib.InternalCodingErrorIf(keyVarsNode.Type != asts.NodeType(NodeTypeParameterList) &&
keyVarsNode.Type != asts.NodeType(NodeTypeMultiIndex))
lib.InternalCodingErrorIf(valVarNode.Type != asts.NodeType(NodeTypeLocalVariable))
seen := make(map[string]bool)
for _, keyVarNode := range keyVarsNode.Children {
lib.InternalCodingErrorIf(keyVarNode.Type != dsl.NodeTypeLocalVariable)
name := string(keyVarNode.Token.Lit)
lib.InternalCodingErrorIf(keyVarNode.Type != asts.NodeType(NodeTypeLocalVariable))
name := tokenLit(keyVarNode)
_, present := seen[name]
if present {
return fmt.Errorf("redefinition of variable %s in the same scope", name)
@ -289,7 +292,7 @@ func validateForLoopMultivariableUniqueNames(astNode *dsl.ASTNode) error {
seen[name] = true
}
valVarName := string(valVarNode.Token.Lit)
valVarName := tokenLit(valVarNode)
if seen[valVarName] {
return fmt.Errorf("redefinition of variable %s in the same scope", valVarName)
}
@ -297,16 +300,22 @@ func validateForLoopMultivariableUniqueNames(astNode *dsl.ASTNode) error {
return nil
}
var VALID_LHS_NODE_TYPES = map[dsl.TNodeType]bool{
dsl.NodeTypeArrayOrMapIndexAccess: true,
dsl.NodeTypeDotOperator: true,
dsl.NodeTypeArraySliceAccess: true,
dsl.NodeTypeDirectFieldValue: true,
dsl.NodeTypeDirectOosvarValue: true,
dsl.NodeTypeEnvironmentVariable: true,
dsl.NodeTypeFullOosvar: true,
dsl.NodeTypeFullSrec: true,
dsl.NodeTypeIndirectFieldValue: true, // includes $[[n]] and $[[[n]]]
dsl.NodeTypeIndirectOosvarValue: true,
dsl.NodeTypeLocalVariable: true,
var VALID_LHS_NODE_TYPES = map[string]bool{
NodeTypeArrayOrMapIndexAccess: true,
NodeTypeDotOperator: true,
NodeTypeArraySliceLoHi: true,
NodeTypeArraySliceHiOnly: true,
NodeTypeArraySliceLoOnly: true,
NodeTypeArraySliceFull: true,
NodeTypeDirectFieldValue: true,
NodeTypeBracedFieldValue: true, // ${foo}, ${x+y}
NodeTypeEnvironmentVariable: true, // ENV["FOO"] = "bar"
NodeTypeDirectOosvarValue: true,
NodeTypeBracedOosvarValue: true, // @{foo}, @{variable.name}
NodeTypeFullOosvar: true,
NodeTypeFullSrec: true,
NodeTypeIndirectFieldValue: true, // includes $[[n]] and $[[[n]]]
NodeTypeIndirectOosvarValue: true,
NodeTypeLocalVariable: true,
"TypedeclLocalVariable": true, // int a = 5; etc
}

View file

@ -9,13 +9,14 @@ import (
"fmt"
"os"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// Returns true if there are no warnings.
func WarnOnAST(
ast *dsl.AST,
ast *asts.AST,
) bool {
variableNamesWrittenTo := make(map[string]bool)
inAssignment := false
@ -80,7 +81,7 @@ func WarnOnAST(
// Returns true if there are no warnings
func warnOnASTAux(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
variableNamesWrittenTo map[string]bool,
inAssignment bool,
) bool {
@ -89,8 +90,8 @@ func warnOnASTAux(
// Check local-variable references, and see if they're reads or writes
// based on the AST parenting of this node.
if astNode.Type == dsl.NodeTypeLocalVariable {
variableName := string(astNode.Token.Lit)
if astNode.Type == asts.NodeType(NodeTypeLocalVariable) {
variableName := tokenLit(astNode)
if inAssignment {
variableNamesWrittenTo[variableName] = true
} else {
@ -99,27 +100,27 @@ func warnOnASTAux(
os.Stderr,
"Variable name %s might not have been assigned yet%s.\n",
variableName,
dsl.TokenToLocationInfo(astNode.Token),
pgpgTokenToLocationInfo(astNode.Token),
)
ok = false
}
}
} else if astNode.Type == dsl.NodeTypeBeginBlock {
} else if astNode.Type == asts.NodeType(NodeTypeBeginBlock) {
// Locals are confined to begin/end blocks and func/subr blocks.
// Reset for this part of the treewalk.
variableNamesWrittenTo = make(map[string]bool)
} else if astNode.Type == dsl.NodeTypeEndBlock {
} else if astNode.Type == asts.NodeType(NodeTypeEndBlock) {
// Locals are confined to begin/end blocks and func/subr blocks.
// Reset for this part of the treewalk.
variableNamesWrittenTo = make(map[string]bool)
} else if astNode.Type == dsl.NodeTypeNamedFunctionDefinition {
} else if astNode.Type == asts.NodeType(NodeTypeNamedFunctionDefinition) {
// Locals are confined to begin/end blocks and func/subr blocks. Reset
// for this part of the treewalk, except mark the parameters as
// defined.
variableNamesWrittenTo = noteParametersForWarnings(astNode)
} else if astNode.Type == dsl.NodeTypeSubroutineDefinition {
} else if astNode.Type == asts.NodeType(NodeTypeSubroutineDefinition) {
// Locals are confined to begin/end blocks and func/subr blocks. Reset
// for this part of the treewalk, except mark the parameters as
// defined.
@ -131,21 +132,21 @@ func warnOnASTAux(
for i, astChild := range astNode.Children {
childInAssignment := inAssignment
if astNode.Type == dsl.NodeTypeAssignment && i == 0 {
if (astNode.Type == asts.NodeType(NodeTypeAssignment) || astNode.Type == asts.NodeType(NodeTypeCompoundAssignment)) && i == 0 {
// LHS of assignment statements
childInAssignment = true
} else if astNode.Type == dsl.NodeTypeForLoopOneVariable && i == 0 {
} else if astNode.Type == asts.NodeType(NodeTypeForLoopOneVariable) && i == 0 {
// The 'k' in 'for (k in $*)'
childInAssignment = true
} else if astNode.Type == dsl.NodeTypeForLoopTwoVariable && (i == 0 || i == 1) {
} else if astNode.Type == asts.NodeType(NodeTypeForLoopTwoVariable) && (i == 0 || i == 1) {
// The 'k' and 'v' in 'for (k,v in $*)'
childInAssignment = true
} else if astNode.Type == dsl.NodeTypeForLoopMultivariable && (i == 0 || i == 1) {
} else if astNode.Type == asts.NodeType(NodeTypeForLoopMultivariable) && (i == 0 || i == 1) {
// The 'k1', 'k2', and 'v' in 'for ((k1,k2),v in $*)'
childInAssignment = true
} else if astNode.Type == dsl.NodeTypeParameterList {
} else if astNode.Type == asts.NodeType(NodeTypeParameterList) {
childInAssignment = true
} else if inAssignment && astNode.Type == dsl.NodeTypeArrayOrMapIndexAccess {
} else if inAssignment && astNode.Type == asts.NodeType(NodeTypeArrayOrMapIndexAccess) {
// In 'z[i] = 1', the 'i' is a read and the 'z' is a write.
//
// mlr --from r put -v -W 'z[i] = 1'
@ -177,31 +178,45 @@ func warnOnASTAux(
return ok
}
// collectParameterNodes flattens ParameterList (which may nest in PGPG grammar) into Parameter nodes.
func collectParameterNodes(parameterListNode *asts.ASTNode) []*asts.ASTNode {
var out []*asts.ASTNode
for _, ch := range parameterListNode.Children {
if ch.Type == asts.NodeType(NodeTypeParameterList) {
out = append(out, collectParameterNodes(ch)...)
} else if ch.Type == asts.NodeType(NodeTypeParameter) {
out = append(out, ch)
}
}
return out
}
// Given a func/subr block, find the names of its parameters. All the
// lib.InternalCodingErrorIf parts are shape-assertions to make sure this code
// is in sync with the BNF grammar which builds the AST from a Miller-DSL
// source string.
//
// PGPG: Parameter has one child which is LocalVariable (not ParameterName).
func noteParametersForWarnings(
astNode *dsl.ASTNode,
astNode *asts.ASTNode,
) map[string]bool {
variableNamesWrittenTo := make(map[string]bool)
lib.InternalCodingErrorIf(
astNode.Type != dsl.NodeTypeNamedFunctionDefinition &&
astNode.Type != dsl.NodeTypeSubroutineDefinition)
astNode.Type != asts.NodeType(NodeTypeNamedFunctionDefinition) &&
astNode.Type != asts.NodeType(NodeTypeSubroutineDefinition))
lib.InternalCodingErrorIf(len(astNode.Children) < 1)
parameterListNode := astNode.Children[0]
lib.InternalCodingErrorIf(parameterListNode.Type != dsl.NodeTypeParameterList)
lib.InternalCodingErrorIf(parameterListNode.Type != asts.NodeType(NodeTypeParameterList))
for _, parameterNode := range parameterListNode.Children {
lib.InternalCodingErrorIf(parameterNode.Type != dsl.NodeTypeParameter)
lib.InternalCodingErrorIf(len(parameterNode.Children) != 1)
parameterNameNode := parameterNode.Children[0]
lib.InternalCodingErrorIf(parameterNameNode.Type != dsl.NodeTypeParameterName)
parameterName := string(parameterNameNode.Token.Lit)
variableNamesWrittenTo[parameterName] = true
// PGPG: ParameterList can nest (FuncParams wraps FuncOrSubrParameterList, which builds list recursively).
for _, ch := range collectParameterNodes(parameterListNode) {
lib.InternalCodingErrorIf(len(ch.Children) != 1)
paramNameNode := ch.Children[0]
lib.InternalCodingErrorIf(paramNameNode.Type != asts.NodeType(NodeTypeLocalVariable))
variableNamesWrittenTo[tokenLit(paramNameNode)] = true
}
return variableNamesWrittenTo

View file

@ -5,21 +5,21 @@ package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/parsing/token"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
"github.com/johnkerl/pgpg/go/lib/pkg/tokens"
)
type WhileLoopNode struct {
conditionNode IEvaluable
conditionToken *token.Token
conditionToken *tokens.Token
statementBlockNode *StatementBlockNode
}
func NewWhileLoopNode(
conditionNode IEvaluable,
conditionToken *token.Token,
conditionToken *tokens.Token,
statementBlockNode *StatementBlockNode,
) *WhileLoopNode {
return &WhileLoopNode{
@ -29,8 +29,8 @@ func NewWhileLoopNode(
}
}
func (root *RootNode) BuildWhileLoopNode(astNode *dsl.ASTNode) (*WhileLoopNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeWhileLoop)
func (root *RootNode) BuildWhileLoopNode(astNode *asts.ASTNode) (*WhileLoopNode, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeWhileLoop))
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
conditionNode, err := root.BuildEvaluableNode(astNode.Children[0])
@ -57,7 +57,7 @@ func (node *WhileLoopNode) Execute(state *runtime.State) (*BlockExitPayload, err
if !isBool {
return nil, fmt.Errorf(
"conditional expression did not evaluate to boolean%s",
dsl.TokenToLocationInfo(node.conditionToken),
pgpgTokenToLocationInfo(node.conditionToken),
)
}
if !boolValue {
@ -89,13 +89,13 @@ func (node *WhileLoopNode) Execute(state *runtime.State) (*BlockExitPayload, err
type DoWhileLoopNode struct {
statementBlockNode *StatementBlockNode
conditionNode IEvaluable
conditionToken *token.Token
conditionToken *tokens.Token
}
func NewDoWhileLoopNode(
statementBlockNode *StatementBlockNode,
conditionNode IEvaluable,
conditionToken *token.Token,
conditionToken *tokens.Token,
) *DoWhileLoopNode {
return &DoWhileLoopNode{
statementBlockNode: statementBlockNode,
@ -104,8 +104,8 @@ func NewDoWhileLoopNode(
}
}
func (root *RootNode) BuildDoWhileLoopNode(astNode *dsl.ASTNode) (*DoWhileLoopNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeDoWhileLoop)
func (root *RootNode) BuildDoWhileLoopNode(astNode *asts.ASTNode) (*DoWhileLoopNode, error) {
lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeDoWhileLoop))
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
statementBlockNode, err := root.BuildStatementBlockNode(astNode.Children[0])
@ -152,7 +152,7 @@ func (node *DoWhileLoopNode) Execute(state *runtime.State) (*BlockExitPayload, e
if !isBool {
return nil, fmt.Errorf(
"conditional expression did not evaluate to boolean%s",
dsl.TokenToLocationInfo(node.conditionToken),
pgpgTokenToLocationInfo(node.conditionToken),
)
}
if !boolValue {

View file

@ -1,16 +0,0 @@
package dsl
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/parsing/token"
)
// TokenToLocationInfo is used to track runtime errors back to source-code locations in DSL
// expressions, so we can have more informative error messages.
func TokenToLocationInfo(sourceToken *token.Token) string {
if sourceToken == nil {
return ""
}
return fmt.Sprintf(" at DSL expression line %d column %d", sourceToken.Pos.Line, sourceToken.Pos.Column)
}

View file

@ -1,26 +1,11 @@
This directory contains a single source file, `mlr.bnf`, which is the
lexical/semantic grammar file for the Miller `put`/`filter` DSL
(domain-specific language) using the GOCC framework. (In a classical Lex/Yacc
framework, there would be separate `mlr.l` and `mlr.y` files; using GOCC, there
is a single `mlr.bnf` file.)
This directory contains a single source file, `mlr.bnf`, which is the lexical/semantic grammar file
for the Miller `put`/`filter` DSL (domain-specific language) using the [PGPG
framework](https://github.com/johnkerl/pgpg). (In a classical Lex/Yacc framework, there would be
separate `mlr.l` and `mlr.y` files; using PGPG, there is a single `mlr.bnf` file.)
All subdirectories of `pkg/parsing/` are autogen code created by GOCC's
processing of `mlr.bnf`. They are nonetheless committed to source control,
since running GOCC takes quite a bit longer than the `go build` does, and the
BNF file doesn't often change. (_BNF_ is for _Backus-Naur Form_ which is the
phrasing of the grammar file that GOCC support.) See the top-level `miller/go`
build scripts for how to rerun GOCC. As of this writing, it's
All subdirectories of `pkg/parsing/` are autogen code created by PGPG's processing of `mlr.bnf`.
They are nonetheless committed to source control, since running PGPG takes a bit longer than the `go
build` does, and the BNF file doesn't often change. (_BNF_ is for _Backus-Naur Form_ which is the
phrasing of the grammar file that PGPG support.)
```
gocc -o pkg/parsing pkg/parsing/mlr.bnf
````
as invoked from the repo base directory -- however you should instead use
```
tools/build-dsl
```
Making changes to `mlr.bnf` requires several minutes to re-run GOCC. For
experimental changes, please see the
[cmd/experiments/dsl_parser](../../../cmd/experiments/dsl_parser) directory.
Run [tools/build-dsl](../../tools/build-dsl) from the repo root to regenerate the lexer and parser.

View file

@ -1,4 +1,4 @@
// Package parsing contains the single source file mlr.bnf, which is the
// lexical/semantic grammar file for the Miller put/filter DSL using the GOCC
// framework. Go source files are autogenerated by GOCC.
// lexical/semantic grammar file for the Miller put/filter DSL using the PGPG
// framework. Go source files are autogenerated by PGPG.
package parsing

View file

@ -1,100 +0,0 @@
// ================================================================
// During the Miller build, after GOCC codegen and Go compile, this is copied
// over the top of GOCC codegen so that we can customize handling of error
// messages.
//
// Source: pkg/parsing/errors.go.template
// Destination: pkg/parsing/errors/errors.go
// ================================================================
package errors
import (
"fmt"
"strings"
"github.com/johnkerl/miller/v6/pkg/parsing/token"
)
type ErrorSymbol interface {
}
type Error struct {
Err error
ErrorToken *token.Token
ErrorSymbols []ErrorSymbol
ExpectedTokens []string
StackTop int
}
func (e *Error) String() string {
w := new(strings.Builder)
fmt.Fprintf(w, "Error")
if e.Err != nil {
fmt.Fprintf(w, " %s\n", e.Err)
} else {
fmt.Fprintf(w, "\n")
}
fmt.Fprintf(w, "Token: type=%d, lit=%s\n", e.ErrorToken.Type, e.ErrorToken.Lit)
fmt.Fprintf(w, "Pos: offset=%d, line=%d, column=%d\n", e.ErrorToken.Pos.Offset, e.ErrorToken.Pos.Line, e.ErrorToken.Pos.Column)
fmt.Fprintf(w, "Expected one of: ")
for _, sym := range e.ExpectedTokens {
fmt.Fprintf(w, "%s ", sym)
}
fmt.Fprintf(w, "ErrorSymbol:\n")
for _, sym := range e.ErrorSymbols {
fmt.Fprintf(w, "%v\n", sym)
}
return w.String()
}
func (e *Error) Error() string {
w := new(strings.Builder)
fmt.Fprintf(
w,
"Parse error on token \"%s\" at line %d column %d.\n",
string(e.ErrorToken.Lit),
e.ErrorToken.Pos.Line,
e.ErrorToken.Pos.Column,
)
if e.Err != nil {
fmt.Fprintf(w, "%+v\n", e.Err)
} else {
suggestSemicolons := false
for _, expected := range e.ExpectedTokens {
if expected == ";" {
suggestSemicolons = true
break
}
}
if suggestSemicolons {
fmt.Fprintf(w, "Please check for missing semicolon.\n")
}
fmt.Fprintf(w, "Expected one of:\n")
//for _, expected := range e.ExpectedTokens {
// fmt.Fprintf(w, "%s ", expected)
//}
// Print a carriage return every so often, in case there are many possible
// next tokens.
line := ""
for _, expected := range e.ExpectedTokens {
if line != "" {
line += " "
}
line += expected
if len(line) > 70 {
fmt.Fprintf(w, " %s\n", line)
line = ""
}
}
if line != "" {
fmt.Fprintf(w, " %s\n", line)
}
}
return w.String()
}

View file

@ -1,3 +0,0 @@
// Package errors contains source code autogenerated by GOCC from the Miller DSL's
// grammar file pkg/parsing/mlr.bnf.
package errors

View file

@ -1,95 +0,0 @@
// ================================================================
// During the Miller build, after GOCC codegen and Go compile, this is copied
// over the top of GOCC codegen so that we can customize handling of error
// messages.
//
// Source: pkg/parsing/errors.go.template
// Destination: pkg/parsing/errors/errors.go
// ================================================================
package errors
import (
"fmt"
"slices"
"strings"
"github.com/johnkerl/miller/v6/pkg/parsing/token"
)
type ErrorSymbol interface {
}
type Error struct {
Err error
ErrorToken *token.Token
ErrorSymbols []ErrorSymbol
ExpectedTokens []string
StackTop int
}
func (e *Error) String() string {
w := new(strings.Builder)
fmt.Fprintf(w, "Error")
if e.Err != nil {
fmt.Fprintf(w, " %s\n", e.Err)
} else {
fmt.Fprintf(w, "\n")
}
fmt.Fprintf(w, "Token: type=%d, lit=%s\n", e.ErrorToken.Type, e.ErrorToken.Lit)
fmt.Fprintf(w, "Pos: offset=%d, line=%d, column=%d\n", e.ErrorToken.Pos.Offset, e.ErrorToken.Pos.Line, e.ErrorToken.Pos.Column)
fmt.Fprintf(w, "Expected one of: ")
for _, sym := range e.ExpectedTokens {
fmt.Fprintf(w, "%s ", sym)
}
fmt.Fprintf(w, "ErrorSymbol:\n")
for _, sym := range e.ErrorSymbols {
fmt.Fprintf(w, "%v\n", sym)
}
return w.String()
}
func (e *Error) Error() string {
w := new(strings.Builder)
fmt.Fprintf(
w,
"Parse error on token \"%s\" at line %d column %d.\n",
string(e.ErrorToken.Lit),
e.ErrorToken.Pos.Line,
e.ErrorToken.Pos.Column,
)
if e.Err != nil {
fmt.Fprintf(w, "%+v\n", e.Err)
} else {
suggestSemicolons := slices.Contains(e.ExpectedTokens, ";")
if suggestSemicolons {
fmt.Fprintf(w, "Please check for missing semicolon.\n")
}
fmt.Fprintf(w, "Expected one of:\n")
//for _, expected := range e.ExpectedTokens {
// fmt.Fprintf(w, "%s ", expected)
//}
// Print a carriage return every so often, in case there are many possible
// next tokens.
line := ""
for _, expected := range e.ExpectedTokens {
if line != "" {
line += " "
}
line += expected
if len(line) > 70 {
fmt.Fprintf(w, " %s\n", line)
line = ""
}
}
if line != "" {
fmt.Fprintf(w, " %s\n", line)
}
}
return w.String()
}

39154
pkg/parsing/gen/lexer.json Normal file

File diff suppressed because it is too large Load diff

662022
pkg/parsing/gen/parser.json Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
// adapter.go provides NewLexer([]byte) for compatibility with pkg/dsl/cst/root.go.
// The PGPG-generated lexer uses NewMlrLexer(io.Reader); this adapter accepts []byte.
package lexer
import (
"bytes"
liblexers "github.com/johnkerl/pgpg/go/lib/pkg/lexers"
)
// NewLexer creates a lexer from a byte slice. This matches the API expected by
// pkg/dsl/cst/root.go (same signature as the former PGPG lexer).
func NewLexer(src []byte) liblexers.AbstractLexer {
return NewMlrLexer(bytes.NewReader(src))
}

View file

@ -1,3 +1,3 @@
// Package lexer contains source code autogenerated by GOCC from the Miller DSL's
// Package lexer contains source code autogenerated by PGPG from the Miller DSL's
// grammar file pkg/parsing/mlr.bnf.
package lexer

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,51 +0,0 @@
// Code generated by gocc; DO NOT EDIT.
package parser
import (
"fmt"
)
type action interface {
act()
String() string
}
type (
accept bool
shift int // value is next state index
reduce int // value is production index
)
func (this accept) act() {}
func (this shift) act() {}
func (this reduce) act() {}
func (this accept) Equal(that action) bool {
if _, ok := that.(accept); ok {
return true
}
return false
}
func (this reduce) Equal(that action) bool {
that1, ok := that.(reduce)
if !ok {
return false
}
return this == that1
}
func (this shift) Equal(that action) bool {
that1, ok := that.(shift)
if !ok {
return false
}
return this == that1
}
func (this accept) String() string { return "accept(0)" }
func (this shift) String() string { return fmt.Sprintf("shift:%d", this) }
func (this reduce) String() string {
return fmt.Sprintf("reduce:%d(%s)", this, productionsTable[this].String)
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +0,0 @@
// Code generated by gocc; DO NOT EDIT.
package parser
// Parser-specific user-defined and entirely-optional context,
// accessible as '$Context' in SDT actions.
type Context interface{}

View file

@ -1,3 +1,3 @@
// Package parser contains source code autogenerated by GOCC from the Miller DSL's
// Package parser contains source code autogenerated by PGPG from the Miller DSL's
// grammar file pkg/parsing/mlr.bnf.
package parser

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,14 +0,0 @@
// Code generated by gocc; DO NOT EDIT.
package token
// Context allows user-defined data to be associated with the
// lexer/scanner to be associated with each token that lexer
// produces.
type Context interface{}
// Sourcer is a Context interface which presents a Source() method
// identifying e.g the filename for the current code.
type Sourcer interface {
Source() string
}

View file

@ -1,3 +0,0 @@
// Package token contains source code autogenerated by GOCC from the Miller DSL's
// grammar file pkg/parsing/mlr.bnf.
package token

View file

@ -1,425 +0,0 @@
// Code generated by gocc; DO NOT EDIT.
package token
import (
"bytes"
"fmt"
"strconv"
"unicode/utf8"
)
type Token struct {
Type
Lit []byte
Pos
}
type Type int
const (
INVALID Type = iota
EOF
)
type Pos struct {
Offset int
Line int
Column int
Context Context
}
func (p Pos) String() string {
// If the context provides a filename, provide a human-readable File:Line:Column representation.
switch src := p.Context.(type) {
case Sourcer:
return fmt.Sprintf("%s:%d:%d", src.Source(), p.Line, p.Column)
default:
return fmt.Sprintf("Pos(offset=%d, line=%d, column=%d)", p.Offset, p.Line, p.Column)
}
}
type TokenMap struct {
typeMap []string
idMap map[string]Type
}
func (m TokenMap) Id(tok Type) string {
if int(tok) < len(m.typeMap) {
return m.typeMap[tok]
}
return "unknown"
}
func (m TokenMap) Type(tok string) Type {
if typ, exist := m.idMap[tok]; exist {
return typ
}
return INVALID
}
func (m TokenMap) TokenString(tok *Token) string {
return fmt.Sprintf("%s(%d,%s)", m.Id(tok.Type), tok.Type, tok.Lit)
}
func (m TokenMap) StringType(typ Type) string {
return fmt.Sprintf("%s(%d)", m.Id(typ), typ)
}
// Equals returns returns true if the token Type and Lit are matches.
func (t *Token) Equals(rhs interface{}) bool {
switch rhsT := rhs.(type) {
case *Token:
return t == rhsT || (t.Type == rhsT.Type && bytes.Equal(t.Lit, rhsT.Lit))
default:
return false
}
}
// CharLiteralValue returns the string value of the char literal.
func (t *Token) CharLiteralValue() string {
return string(t.Lit[1 : len(t.Lit)-1])
}
// Float32Value returns the float32 value of the token or an error if the token literal does not
// denote a valid float32.
func (t *Token) Float32Value() (float32, error) {
if v, err := strconv.ParseFloat(string(t.Lit), 32); err != nil {
return 0, err
} else {
return float32(v), nil
}
}
// Float64Value returns the float64 value of the token or an error if the token literal does not
// denote a valid float64.
func (t *Token) Float64Value() (float64, error) {
return strconv.ParseFloat(string(t.Lit), 64)
}
// IDValue returns the string representation of an identifier token.
func (t *Token) IDValue() string {
return string(t.Lit)
}
// Int32Value returns the int32 value of the token or an error if the token literal does not
// denote a valid float64.
func (t *Token) Int32Value() (int32, error) {
if v, err := strconv.ParseInt(string(t.Lit), 10, 64); err != nil {
return 0, err
} else {
return int32(v), nil
}
}
// Int64Value returns the int64 value of the token or an error if the token literal does not
// denote a valid float64.
func (t *Token) Int64Value() (int64, error) {
return strconv.ParseInt(string(t.Lit), 10, 64)
}
// UTF8Rune decodes the UTF8 rune in the token literal. It returns utf8.RuneError if
// the token literal contains an invalid rune.
func (t *Token) UTF8Rune() (rune, error) {
r, _ := utf8.DecodeRune(t.Lit)
if r == utf8.RuneError {
err := fmt.Errorf("Invalid rune")
return r, err
}
return r, nil
}
// StringValue returns the string value of the token literal.
func (t *Token) StringValue() string {
return string(t.Lit[1 : len(t.Lit)-1])
}
var TokMap = TokenMap{
typeMap: []string{
"INVALID",
"␚",
"empty",
";",
"{",
"}",
"=",
"unset",
"filter",
">",
">>",
"|",
"stdout",
"stderr",
"print",
",",
"printn",
"eprint",
"eprintn",
"dump",
"edump",
"tee",
"emitf",
"emit1",
"emit",
"(",
")",
"emitp",
"field_name",
"$[",
"]",
"braced_field_name",
"full_srec",
"oosvar_name",
"@[",
"braced_oosvar_name",
"full_oosvar",
"all",
"non_sigil_name",
"arr",
"bool",
"float",
"int",
"map",
"num",
"str",
"var",
"funct",
"||=",
"^^=",
"&&=",
"??=",
"???=",
"|=",
"&=",
"^=",
"<<=",
">>=",
">>>=",
"+=",
".=",
"-=",
"*=",
"/=",
"//=",
"%=",
"**=",
"?",
":",
"||",
"^^",
"&&",
"=~",
"!=~",
"==",
"!=",
"<=>",
">=",
"<",
"<=",
"^",
"&",
"<<",
">>>",
"+",
"-",
".+",
".-",
"*",
"/",
"//",
"%",
".*",
"./",
".//",
".",
"!",
"~",
"??",
"???",
"**",
"string_literal",
"regex_case_insensitive",
"int_literal",
"float_literal",
"boolean_literal",
"null_literal",
"inf_literal",
"nan_literal",
"const_M_PI",
"const_M_E",
"panic",
"[",
"ctx_IPS",
"ctx_IFS",
"ctx_IRS",
"ctx_OPS",
"ctx_OFS",
"ctx_ORS",
"ctx_FLATSEP",
"ctx_NF",
"ctx_NR",
"ctx_FNR",
"ctx_FILENAME",
"ctx_FILENUM",
"env",
"call",
"begin",
"end",
"if",
"elif",
"else",
"while",
"do",
"for",
"in",
"break",
"continue",
"func",
"subr",
"return",
},
idMap: map[string]Type{
"INVALID": 0,
"␚": 1,
"empty": 2,
";": 3,
"{": 4,
"}": 5,
"=": 6,
"unset": 7,
"filter": 8,
">": 9,
">>": 10,
"|": 11,
"stdout": 12,
"stderr": 13,
"print": 14,
",": 15,
"printn": 16,
"eprint": 17,
"eprintn": 18,
"dump": 19,
"edump": 20,
"tee": 21,
"emitf": 22,
"emit1": 23,
"emit": 24,
"(": 25,
")": 26,
"emitp": 27,
"field_name": 28,
"$[": 29,
"]": 30,
"braced_field_name": 31,
"full_srec": 32,
"oosvar_name": 33,
"@[": 34,
"braced_oosvar_name": 35,
"full_oosvar": 36,
"all": 37,
"non_sigil_name": 38,
"arr": 39,
"bool": 40,
"float": 41,
"int": 42,
"map": 43,
"num": 44,
"str": 45,
"var": 46,
"funct": 47,
"||=": 48,
"^^=": 49,
"&&=": 50,
"??=": 51,
"???=": 52,
"|=": 53,
"&=": 54,
"^=": 55,
"<<=": 56,
">>=": 57,
">>>=": 58,
"+=": 59,
".=": 60,
"-=": 61,
"*=": 62,
"/=": 63,
"//=": 64,
"%=": 65,
"**=": 66,
"?": 67,
":": 68,
"||": 69,
"^^": 70,
"&&": 71,
"=~": 72,
"!=~": 73,
"==": 74,
"!=": 75,
"<=>": 76,
">=": 77,
"<": 78,
"<=": 79,
"^": 80,
"&": 81,
"<<": 82,
">>>": 83,
"+": 84,
"-": 85,
".+": 86,
".-": 87,
"*": 88,
"/": 89,
"//": 90,
"%": 91,
".*": 92,
"./": 93,
".//": 94,
".": 95,
"!": 96,
"~": 97,
"??": 98,
"???": 99,
"**": 100,
"string_literal": 101,
"regex_case_insensitive": 102,
"int_literal": 103,
"float_literal": 104,
"boolean_literal": 105,
"null_literal": 106,
"inf_literal": 107,
"nan_literal": 108,
"const_M_PI": 109,
"const_M_E": 110,
"panic": 111,
"[": 112,
"ctx_IPS": 113,
"ctx_IFS": 114,
"ctx_IRS": 115,
"ctx_OPS": 116,
"ctx_OFS": 117,
"ctx_ORS": 118,
"ctx_FLATSEP": 119,
"ctx_NF": 120,
"ctx_NR": 121,
"ctx_FNR": 122,
"ctx_FILENAME": 123,
"ctx_FILENUM": 124,
"env": 125,
"call": 126,
"begin": 127,
"end": 128,
"if": 129,
"elif": 130,
"else": 131,
"while": 132,
"do": 133,
"for": 134,
"in": 135,
"break": 136,
"continue": 137,
"func": 138,
"subr": 139,
"return": 140,
},
}

View file

@ -1,3 +0,0 @@
// Package util contains source code autogenerated by GOCC from the Miller DSL's
// grammar file pkg/parsing/mlr.bnf.
package util

View file

@ -1,101 +0,0 @@
// Code generated by gocc; DO NOT EDIT.
package util
import (
"fmt"
"strconv"
"unicode"
"unicode/utf8"
)
// Interface.
// RuneValue will convert the literal value of a scanned token to a rune.
func RuneValue(lit []byte) rune {
if lit[1] == '\\' {
return escapeCharVal(lit)
}
r, size := utf8.DecodeRune(lit[1:])
if size != len(lit)-2 {
panic(fmt.Sprintf("Error decoding rune. Lit: %s, rune: %d, size%d\n", lit, r, size))
}
return r
}
// UintValue will attempt to parse a byte-slice as a signed base-10 64-bit integer.
func IntValue(lit []byte) (int64, error) {
return strconv.ParseInt(string(lit), 10, 64)
}
// UintValue will attempt to parse a byte-slice as an unsigned base-10 64-bit integer.
func UintValue(lit []byte) (uint64, error) {
return strconv.ParseUint(string(lit), 10, 64)
}
// Helpers.
func escapeCharVal(lit []byte) rune {
var i, base, max uint32
offset := 2
switch lit[offset] {
case 'a':
return '\a'
case 'b':
return '\b'
case 'f':
return '\f'
case 'n':
return '\n'
case 'r':
return '\r'
case 't':
return '\t'
case 'v':
return '\v'
case '\\':
return '\\'
case '\'':
return '\''
case '0', '1', '2', '3', '4', '5', '6', '7':
i, base, max = 3, 8, 255
case 'x':
i, base, max = 2, 16, 255
offset++
case 'u':
i, base, max = 4, 16, unicode.MaxRune
offset++
case 'U':
i, base, max = 8, 16, unicode.MaxRune
offset++
default:
panic(fmt.Sprintf("Error decoding character literal: %s\n", lit))
}
var x uint32
for ; i > 0 && offset < len(lit)-1; i-- {
ch, size := utf8.DecodeRune(lit[offset:])
offset += size
d := uint32(digitVal(ch))
if d >= base {
panic(fmt.Sprintf("charVal(%s): illegal character (%c) in escape sequence. size=%d, offset=%d", lit, ch, size, offset))
}
x = x*base + d
}
if x > max || 0xD800 <= x && x < 0xE000 {
panic(fmt.Sprintf("Error decoding escape char value. Lit:%s, offset:%d, escape sequence is invalid Unicode code point\n", lit, offset))
}
return rune(x)
}
func digitVal(ch rune) int {
switch {
case '0' <= ch && ch <= '9':
return int(ch) - '0'
case 'a' <= ch && ch <= 'f':
return int(ch) - 'a' + 10
case 'A' <= ch && ch <= 'F':
return int(ch) - 'A' + 10
}
return 16 // larger than any legal digit val
}

View file

@ -1,39 +0,0 @@
// Code generated by gocc; DO NOT EDIT.
package util
import (
"fmt"
)
func RuneToString(r rune) string {
if r >= 0x20 && r < 0x7f {
return fmt.Sprintf("'%c'", r)
}
switch r {
case 0x07:
return "'\\a'"
case 0x08:
return "'\\b'"
case 0x0C:
return "'\\f'"
case 0x0A:
return "'\\n'"
case 0x0D:
return "'\\r'"
case 0x09:
return "'\\t'"
case 0x0b:
return "'\\v'"
case 0x5c:
return "'\\\\\\'"
case 0x27:
return "'\\''"
case 0x22:
return "'\\\"'"
}
if r < 0x10000 {
return fmt.Sprintf("\\u%04x", r)
}
return fmt.Sprintf("\\U%08x", r)
}

View file

@ -21,9 +21,9 @@ import (
"fmt"
"strings"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/dsl/cst"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
func (repl *Repl) handleDSLStringImmediate(dslString string, doWarnings bool) error {
@ -51,7 +51,7 @@ func (repl *Repl) handleDSLStringAux(
cst.DSLInstanceTypeREPL,
isReplImmediate,
doWarnings,
func(dslString string, astNode *dsl.AST) {
func(dslString string, astNode *asts.AST) {
if repl.astPrintMode == ASTPrintParex {
astNode.PrintParex()
} else if repl.astPrintMode == ASTPrintParexOneLine {

View file

@ -9,11 +9,11 @@ import (
"strings"
"github.com/johnkerl/miller/v6/pkg/colorizer"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/dsl/cst"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/types"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
// Types for the lookup table.
@ -462,7 +462,7 @@ func handleSkipOrProcessUntil(repl *Repl, dslString string, processingNotSkippin
cst.DSLInstanceTypeREPL,
true, // isReplImmediate
repl.doWarnings,
func(dslString string, astNode *dsl.AST) {
func(dslString string, astNode *asts.AST) {
if repl.astPrintMode == ASTPrintParex {
astNode.PrintParex()
} else if repl.astPrintMode == ASTPrintParexOneLine {

View file

@ -6,12 +6,12 @@ import (
"strings"
"github.com/johnkerl/miller/v6/pkg/cli"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/dsl/cst"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/runtime"
"github.com/johnkerl/miller/v6/pkg/types"
"github.com/johnkerl/pgpg/go/lib/pkg/asts"
)
const verbNamePut = "put"
@ -425,7 +425,7 @@ func NewTransformerPut(
false, // isReplImmediate
doWarnings,
func(dslString string, astNode *dsl.AST) {
func(dslString string, astNode *asts.AST) {
if echoDSLString {
fmt.Println("DSL EXPRESSION:")

View file

@ -2,12 +2,14 @@ DSL EXPRESSION:
begin{begin{@x=1}}
AST:
* statement block
* begin block
* statement block
* begin block
* statement block
* assignment "="
* direct oosvar value "x"
* int literal "1"
"block" [tt:block] [nt:StatementBlock]
"begin" [tt:kw_begin] [nt:BeginBlock]
"block" [tt:block] [nt:StatementBlockInBraces]
"block" [tt:block] [nt:StatementBlock]
"begin" [tt:kw_begin] [nt:BeginBlock]
"block" [tt:block] [nt:StatementBlockInBraces]
"block" [tt:block] [nt:StatementBlock]
"=" [tt:equals] [nt:Assignment]
"@x" [tt:oosvar_name] [nt:DirectOosvarValue]
"1" [tt:int_literal] [nt:int_literal]

View file

@ -2,13 +2,15 @@ DSL EXPRESSION:
true{begin{@x=1}}
AST:
* statement block
* cond block
* bool literal "true"
* statement block
* begin block
* statement block
* assignment "="
* direct oosvar value "x"
* int literal "1"
"block" [tt:block] [nt:StatementBlock]
"true" [tt:literal_true] [nt:CondBlock]
"true" [tt:literal_true] [nt:bool_literal]
"block" [tt:block] [nt:StatementBlockInBraces]
"block" [tt:block] [nt:StatementBlock]
"begin" [tt:kw_begin] [nt:BeginBlock]
"block" [tt:block] [nt:StatementBlockInBraces]
"block" [tt:block] [nt:StatementBlock]
"=" [tt:equals] [nt:Assignment]
"@x" [tt:oosvar_name] [nt:DirectOosvarValue]
"1" [tt:int_literal] [nt:int_literal]

View file

@ -2,12 +2,14 @@ DSL EXPRESSION:
end{end{@x=1}}
AST:
* statement block
* end block
* statement block
* end block
* statement block
* assignment "="
* direct oosvar value "x"
* int literal "1"
"block" [tt:block] [nt:StatementBlock]
"end" [tt:kw_end] [nt:EndBlock]
"block" [tt:block] [nt:StatementBlockInBraces]
"block" [tt:block] [nt:StatementBlock]
"end" [tt:kw_end] [nt:EndBlock]
"block" [tt:block] [nt:StatementBlockInBraces]
"block" [tt:block] [nt:StatementBlock]
"=" [tt:equals] [nt:Assignment]
"@x" [tt:oosvar_name] [nt:DirectOosvarValue]
"1" [tt:int_literal] [nt:int_literal]

View file

@ -2,13 +2,15 @@ DSL EXPRESSION:
true{end{@x=1}}
AST:
* statement block
* cond block
* bool literal "true"
* statement block
* end block
* statement block
* assignment "="
* direct oosvar value "x"
* int literal "1"
"block" [tt:block] [nt:StatementBlock]
"true" [tt:literal_true] [nt:CondBlock]
"true" [tt:literal_true] [nt:bool_literal]
"block" [tt:block] [nt:StatementBlockInBraces]
"block" [tt:block] [nt:StatementBlock]
"end" [tt:kw_end] [nt:EndBlock]
"block" [tt:block] [nt:StatementBlockInBraces]
"block" [tt:block] [nt:StatementBlock]
"=" [tt:equals] [nt:Assignment]
"@x" [tt:oosvar_name] [nt:DirectOosvarValue]
"1" [tt:int_literal] [nt:int_literal]

Some files were not shown because too many files have changed in this diff Show more