mirror of https://github.com/deuill/grawkit.git
play: Update Go, use `embed` for static content
This updates to the latest Go version for building the default OCI container for the Grawkit playground, and switches to using the `embed` package for serving static content.
This commit is contained in:
parent
3b8c5eca21
commit
e1208d67bd
|
@ -0,0 +1,16 @@
|
||||||
|
FROM docker.io/golang:1.19 AS builder
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
COPY play/ /src/
|
||||||
|
RUN go build -o /play play.go
|
||||||
|
|
||||||
|
FROM docker.io/debian:stable-slim
|
||||||
|
WORKDIR /play
|
||||||
|
|
||||||
|
COPY --from=builder /play .
|
||||||
|
COPY grawkit .
|
||||||
|
|
||||||
|
USER nobody
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["/play/play", "-script-path", "/play/grawkit", "-listen-address", ":8080"]
|
|
@ -1,17 +0,0 @@
|
||||||
FROM golang:1.13 AS builder
|
|
||||||
WORKDIR /mnt
|
|
||||||
|
|
||||||
COPY play/go.mod play/go.sum play/play.go /mnt/
|
|
||||||
RUN go build -o play play.go
|
|
||||||
|
|
||||||
FROM debian:stable-slim
|
|
||||||
WORKDIR /play
|
|
||||||
|
|
||||||
COPY --from=builder /mnt/play .
|
|
||||||
COPY grawkit .
|
|
||||||
COPY play/static static
|
|
||||||
|
|
||||||
USER nobody
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
CMD ["/play/play", "-script-path", "grawkit", "-listen-address", ":8080"]
|
|
|
@ -1,5 +1,5 @@
|
||||||
module github.com/deuill/grawkit/play
|
module github.com/deuill/grawkit/play
|
||||||
|
|
||||||
go 1.17
|
go 1.19
|
||||||
|
|
||||||
require github.com/benhoyt/goawk v1.13.0
|
require github.com/benhoyt/goawk v1.20.0
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
github.com/benhoyt/goawk v1.13.0 h1:/Iu42ErHsT5vHrpWyewpI98hB2PHBk66o+oLZs4drPs=
|
github.com/benhoyt/goawk v1.20.0 h1:oz81agTfP/8Z7afMvmOwX4Ms9qTtGhZxPEzHCycIFds=
|
||||||
github.com/benhoyt/goawk v1.13.0/go.mod h1:UKzPyqDh9O7HZ/ftnU33MYlAP2rPbXdwQ+OVlEOPsjM=
|
github.com/benhoyt/goawk v1.20.0/go.mod h1:Dp3jBsApuiItYR9atsCm//q/70OnqjihLh5WkU6eW7U=
|
||||||
|
|
44
play/play.go
44
play/play.go
|
@ -5,7 +5,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -17,6 +16,9 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
// Internal packages.
|
||||||
|
"github.com/deuill/grawkit/play/static"
|
||||||
|
|
||||||
// Third-party packages
|
// Third-party packages
|
||||||
"github.com/benhoyt/goawk/interp"
|
"github.com/benhoyt/goawk/interp"
|
||||||
"github.com/benhoyt/goawk/parser"
|
"github.com/benhoyt/goawk/parser"
|
||||||
|
@ -33,21 +35,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Command-line flags to parse.
|
|
||||||
scriptPath = flag.String("script-path", "../grawkit", "The path to the Grawkit script")
|
scriptPath = flag.String("script-path", "../grawkit", "The path to the Grawkit script")
|
||||||
staticDir = flag.String("static-dir", "static", "The directory under which static files can be found")
|
|
||||||
listenAddress = flag.String("listen-address", "localhost:8080", "The default address to listen on")
|
listenAddress = flag.String("listen-address", "localhost:8080", "The default address to listen on")
|
||||||
|
|
||||||
index *template.Template // The base template to render.
|
index *template.Template // The base template to render.
|
||||||
program *parser.Program // The parsed version of the Grawkit script.
|
program *parser.Program // The parsed version of the Grawkit script.
|
||||||
)
|
)
|
||||||
|
|
||||||
type templateData struct {
|
|
||||||
Content string
|
|
||||||
Preview string
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseContent accepts un-filtered POST form content, and returns the content to render as a string.
|
// ParseContent accepts un-filtered POST form content, and returns the content to render as a string.
|
||||||
// An error is returned if the content is missing or otherwise invalid.
|
// An error is returned if the content is missing or otherwise invalid.
|
||||||
func parseContent(form url.Values) (string, error) {
|
func parseContent(form url.Values) (string, error) {
|
||||||
|
@ -70,7 +64,11 @@ func parseContent(form url.Values) (string, error) {
|
||||||
func handleRequest(w http.ResponseWriter, r *http.Request) {
|
func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
// Handle template rendering on root path.
|
// Handle template rendering on root path.
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
var data templateData
|
var data struct {
|
||||||
|
Content string
|
||||||
|
Preview string
|
||||||
|
Error string
|
||||||
|
}
|
||||||
var outbuf, errbuf bytes.Buffer
|
var outbuf, errbuf bytes.Buffer
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
|
@ -117,22 +115,8 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get sanitized filename for request path given.
|
|
||||||
name := path.Join(*staticDir, path.Clean(r.URL.Path))
|
|
||||||
|
|
||||||
// Check if a file exists for the path requested.
|
|
||||||
stat, err := os.Stat(name)
|
|
||||||
if os.IsNotExist(err) || stat != nil && stat.IsDir() {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
code := http.StatusInternalServerError
|
|
||||||
http.Error(w, http.StatusText(code), code)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve file as fallback.
|
// Serve file as fallback.
|
||||||
http.ServeFile(w, r, name)
|
http.FileServer(http.FS(static.FS)).ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup reads configuration flags and initializes global state for the service, returning an error
|
// Setup reads configuration flags and initializes global state for the service, returning an error
|
||||||
|
@ -144,17 +128,17 @@ func setup() error {
|
||||||
// Set up and parse known template files.
|
// Set up and parse known template files.
|
||||||
var err error
|
var err error
|
||||||
var files = []string{
|
var files = []string{
|
||||||
path.Join(*staticDir, "template", "index.template"),
|
path.Join("template", "index.template"),
|
||||||
path.Join(*staticDir, "template", "default-content.template"),
|
path.Join("template", "default-content.template"),
|
||||||
path.Join(*staticDir, "template", "default-preview.template"),
|
path.Join("template", "default-preview.template"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if index, err = template.ParseFiles(files...); err != nil {
|
if index, err = template.ParseFS(static.FS, files...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse Grawkit script into concrete representation.
|
// Parse Grawkit script into concrete representation.
|
||||||
if script, err := ioutil.ReadFile(*scriptPath); err != nil {
|
if script, err := os.ReadFile(*scriptPath); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if program, err = parser.ParseProgram(script, nil); err != nil {
|
} else if program, err = parser.ParseProgram(script, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -51,10 +51,8 @@ a:hover, a:active {
|
||||||
border: 0.2rem solid #333;
|
border: 0.2rem solid #333;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
color: #fefefe;
|
color: #fefefe;
|
||||||
font-family: monospace;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
text-transform: none;
|
|
||||||
transition: background 0.2s ease, border 0.2s ease, color 0.2s ease;
|
transition: background 0.2s ease, border 0.2s ease, color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package static
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
// FS is an [fs.FS] implementation containing all static files needed for serving the Grawkit
|
||||||
|
// playground.
|
||||||
|
//
|
||||||
|
//go:embed *
|
||||||
|
var FS embed.FS
|
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 Ben Hoyt
|
Copyright (c) 2022 Ben Hoyt
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -10,6 +10,35 @@ import (
|
||||||
. "github.com/benhoyt/goawk/lexer"
|
. "github.com/benhoyt/goawk/lexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Program is an entire AWK program.
|
||||||
|
type Program struct {
|
||||||
|
Begin []Stmts
|
||||||
|
Actions []Action
|
||||||
|
End []Stmts
|
||||||
|
Functions []Function
|
||||||
|
Scalars map[string]int
|
||||||
|
Arrays map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns an indented, pretty-printed version of the parsed
|
||||||
|
// program.
|
||||||
|
func (p *Program) String() string {
|
||||||
|
parts := []string{}
|
||||||
|
for _, ss := range p.Begin {
|
||||||
|
parts = append(parts, "BEGIN {\n"+ss.String()+"}")
|
||||||
|
}
|
||||||
|
for _, a := range p.Actions {
|
||||||
|
parts = append(parts, a.String())
|
||||||
|
}
|
||||||
|
for _, ss := range p.End {
|
||||||
|
parts = append(parts, "END {\n"+ss.String()+"}")
|
||||||
|
}
|
||||||
|
for _, function := range p.Functions {
|
||||||
|
parts = append(parts, function.String())
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
// Stmts is a block containing multiple statements.
|
// Stmts is a block containing multiple statements.
|
||||||
type Stmts []Stmt
|
type Stmts []Stmt
|
||||||
|
|
||||||
|
@ -53,24 +82,25 @@ type Expr interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// All these types implement the Expr interface.
|
// All these types implement the Expr interface.
|
||||||
func (e *FieldExpr) expr() {}
|
func (e *FieldExpr) expr() {}
|
||||||
func (e *UnaryExpr) expr() {}
|
func (e *NamedFieldExpr) expr() {}
|
||||||
func (e *BinaryExpr) expr() {}
|
func (e *UnaryExpr) expr() {}
|
||||||
func (e *ArrayExpr) expr() {}
|
func (e *BinaryExpr) expr() {}
|
||||||
func (e *InExpr) expr() {}
|
func (e *ArrayExpr) expr() {}
|
||||||
func (e *CondExpr) expr() {}
|
func (e *InExpr) expr() {}
|
||||||
func (e *NumExpr) expr() {}
|
func (e *CondExpr) expr() {}
|
||||||
func (e *StrExpr) expr() {}
|
func (e *NumExpr) expr() {}
|
||||||
func (e *RegExpr) expr() {}
|
func (e *StrExpr) expr() {}
|
||||||
func (e *VarExpr) expr() {}
|
func (e *RegExpr) expr() {}
|
||||||
func (e *IndexExpr) expr() {}
|
func (e *VarExpr) expr() {}
|
||||||
func (e *AssignExpr) expr() {}
|
func (e *IndexExpr) expr() {}
|
||||||
func (e *AugAssignExpr) expr() {}
|
func (e *AssignExpr) expr() {}
|
||||||
func (e *IncrExpr) expr() {}
|
func (e *AugAssignExpr) expr() {}
|
||||||
func (e *CallExpr) expr() {}
|
func (e *IncrExpr) expr() {}
|
||||||
func (e *UserCallExpr) expr() {}
|
func (e *CallExpr) expr() {}
|
||||||
func (e *MultiExpr) expr() {}
|
func (e *UserCallExpr) expr() {}
|
||||||
func (e *GetlineExpr) expr() {}
|
func (e *MultiExpr) expr() {}
|
||||||
|
func (e *GetlineExpr) expr() {}
|
||||||
|
|
||||||
// FieldExpr is an expression like $0.
|
// FieldExpr is an expression like $0.
|
||||||
type FieldExpr struct {
|
type FieldExpr struct {
|
||||||
|
@ -81,6 +111,15 @@ func (e *FieldExpr) String() string {
|
||||||
return "$" + e.Index.String()
|
return "$" + e.Index.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NamedFieldExpr is an expression like @"name".
|
||||||
|
type NamedFieldExpr struct {
|
||||||
|
Field Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *NamedFieldExpr) String() string {
|
||||||
|
return "@" + e.Field.String()
|
||||||
|
}
|
||||||
|
|
||||||
// UnaryExpr is an expression like -1234.
|
// UnaryExpr is an expression like -1234.
|
||||||
type UnaryExpr struct {
|
type UnaryExpr struct {
|
||||||
Op Token
|
Op Token
|
||||||
|
@ -155,7 +194,11 @@ type NumExpr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *NumExpr) String() string {
|
func (e *NumExpr) String() string {
|
||||||
return fmt.Sprintf("%.6g", e.Value)
|
if e.Value == float64(int(e.Value)) {
|
||||||
|
return strconv.Itoa(int(e.Value))
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%.6g", e.Value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrExpr is a literal string like "foo".
|
// StrExpr is a literal string like "foo".
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
package ast
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
V_ILLEGAL = iota
|
V_ILLEGAL = iota
|
||||||
V_ARGC
|
V_ARGC
|
||||||
|
@ -9,11 +13,13 @@ const (
|
||||||
V_FILENAME
|
V_FILENAME
|
||||||
V_FNR
|
V_FNR
|
||||||
V_FS
|
V_FS
|
||||||
|
V_INPUTMODE
|
||||||
V_NF
|
V_NF
|
||||||
V_NR
|
V_NR
|
||||||
V_OFMT
|
V_OFMT
|
||||||
V_OFS
|
V_OFS
|
||||||
V_ORS
|
V_ORS
|
||||||
|
V_OUTPUTMODE
|
||||||
V_RLENGTH
|
V_RLENGTH
|
||||||
V_RS
|
V_RS
|
||||||
V_RSTART
|
V_RSTART
|
||||||
|
@ -24,21 +30,23 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var specialVars = map[string]int{
|
var specialVars = map[string]int{
|
||||||
"ARGC": V_ARGC,
|
"ARGC": V_ARGC,
|
||||||
"CONVFMT": V_CONVFMT,
|
"CONVFMT": V_CONVFMT,
|
||||||
"FILENAME": V_FILENAME,
|
"FILENAME": V_FILENAME,
|
||||||
"FNR": V_FNR,
|
"FNR": V_FNR,
|
||||||
"FS": V_FS,
|
"FS": V_FS,
|
||||||
"NF": V_NF,
|
"INPUTMODE": V_INPUTMODE,
|
||||||
"NR": V_NR,
|
"NF": V_NF,
|
||||||
"OFMT": V_OFMT,
|
"NR": V_NR,
|
||||||
"OFS": V_OFS,
|
"OFMT": V_OFMT,
|
||||||
"ORS": V_ORS,
|
"OFS": V_OFS,
|
||||||
"RLENGTH": V_RLENGTH,
|
"ORS": V_ORS,
|
||||||
"RS": V_RS,
|
"OUTPUTMODE": V_OUTPUTMODE,
|
||||||
"RSTART": V_RSTART,
|
"RLENGTH": V_RLENGTH,
|
||||||
"RT": V_RT,
|
"RS": V_RS,
|
||||||
"SUBSEP": V_SUBSEP,
|
"RSTART": V_RSTART,
|
||||||
|
"RT": V_RT,
|
||||||
|
"SUBSEP": V_SUBSEP,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpecialVarIndex returns the "index" of the special variable, or 0
|
// SpecialVarIndex returns the "index" of the special variable, or 0
|
||||||
|
@ -46,3 +54,47 @@ var specialVars = map[string]int{
|
||||||
func SpecialVarIndex(name string) int {
|
func SpecialVarIndex(name string) int {
|
||||||
return specialVars[name]
|
return specialVars[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SpecialVarName returns the name of the special variable by index.
|
||||||
|
func SpecialVarName(index int) string {
|
||||||
|
switch index {
|
||||||
|
case V_ILLEGAL:
|
||||||
|
return "ILLEGAL"
|
||||||
|
case V_ARGC:
|
||||||
|
return "ARGC"
|
||||||
|
case V_CONVFMT:
|
||||||
|
return "CONVFMT"
|
||||||
|
case V_FILENAME:
|
||||||
|
return "FILENAME"
|
||||||
|
case V_FNR:
|
||||||
|
return "FNR"
|
||||||
|
case V_FS:
|
||||||
|
return "FS"
|
||||||
|
case V_INPUTMODE:
|
||||||
|
return "INPUTMODE"
|
||||||
|
case V_NF:
|
||||||
|
return "NF"
|
||||||
|
case V_NR:
|
||||||
|
return "NR"
|
||||||
|
case V_OFMT:
|
||||||
|
return "OFMT"
|
||||||
|
case V_OFS:
|
||||||
|
return "OFS"
|
||||||
|
case V_ORS:
|
||||||
|
return "ORS"
|
||||||
|
case V_OUTPUTMODE:
|
||||||
|
return "OUTPUTMODE"
|
||||||
|
case V_RLENGTH:
|
||||||
|
return "RLENGTH"
|
||||||
|
case V_RS:
|
||||||
|
return "RS"
|
||||||
|
case V_RSTART:
|
||||||
|
return "RSTART"
|
||||||
|
case V_RT:
|
||||||
|
return "RT"
|
||||||
|
case V_SUBSEP:
|
||||||
|
return "SUBSEP"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("<unknown special var %d>", index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
1005
play/vendor/github.com/benhoyt/goawk/internal/compiler/compiler.go
generated
vendored
Normal file
1005
play/vendor/github.com/benhoyt/goawk/internal/compiler/compiler.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
495
play/vendor/github.com/benhoyt/goawk/internal/compiler/disassembler.go
generated
vendored
Normal file
495
play/vendor/github.com/benhoyt/goawk/internal/compiler/disassembler.go
generated
vendored
Normal file
|
@ -0,0 +1,495 @@
|
||||||
|
// Disassembles compiled program to text assembly instructions
|
||||||
|
|
||||||
|
package compiler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/benhoyt/goawk/internal/ast"
|
||||||
|
"github.com/benhoyt/goawk/lexer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Disassemble writes a human-readable form of the program's virtual machine
|
||||||
|
// instructions to writer.
|
||||||
|
func (p *Program) Disassemble(writer io.Writer) error {
|
||||||
|
if p.Begin != nil {
|
||||||
|
d := &disassembler{
|
||||||
|
program: p,
|
||||||
|
writer: writer,
|
||||||
|
code: p.Begin,
|
||||||
|
nativeFuncNames: p.nativeFuncNames,
|
||||||
|
}
|
||||||
|
err := d.disassemble("BEGIN")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, action := range p.Actions {
|
||||||
|
switch len(action.Pattern) {
|
||||||
|
case 0:
|
||||||
|
// Nothing to do here.
|
||||||
|
case 1:
|
||||||
|
d := &disassembler{
|
||||||
|
program: p,
|
||||||
|
writer: writer,
|
||||||
|
code: action.Pattern[0],
|
||||||
|
nativeFuncNames: p.nativeFuncNames,
|
||||||
|
}
|
||||||
|
err := d.disassemble("pattern")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
d := &disassembler{
|
||||||
|
program: p,
|
||||||
|
writer: writer,
|
||||||
|
code: action.Pattern[0],
|
||||||
|
nativeFuncNames: p.nativeFuncNames,
|
||||||
|
}
|
||||||
|
err := d.disassemble("start")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d = &disassembler{
|
||||||
|
program: p,
|
||||||
|
writer: writer,
|
||||||
|
code: action.Pattern[1],
|
||||||
|
nativeFuncNames: p.nativeFuncNames,
|
||||||
|
}
|
||||||
|
err = d.disassemble("stop")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(action.Body) > 0 {
|
||||||
|
d := &disassembler{
|
||||||
|
program: p,
|
||||||
|
writer: writer,
|
||||||
|
code: action.Body,
|
||||||
|
nativeFuncNames: p.nativeFuncNames,
|
||||||
|
}
|
||||||
|
err := d.disassemble("{ body }")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.End != nil {
|
||||||
|
d := &disassembler{
|
||||||
|
program: p,
|
||||||
|
writer: writer,
|
||||||
|
code: p.End,
|
||||||
|
nativeFuncNames: p.nativeFuncNames,
|
||||||
|
}
|
||||||
|
err := d.disassemble("END")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, f := range p.Functions {
|
||||||
|
d := &disassembler{
|
||||||
|
program: p,
|
||||||
|
writer: writer,
|
||||||
|
code: f.Body,
|
||||||
|
nativeFuncNames: p.nativeFuncNames,
|
||||||
|
funcIndex: i,
|
||||||
|
}
|
||||||
|
err := d.disassemble("function " + f.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disassembles a single block of opcodes.
|
||||||
|
type disassembler struct {
|
||||||
|
program *Program
|
||||||
|
writer io.Writer
|
||||||
|
code []Opcode
|
||||||
|
nativeFuncNames []string
|
||||||
|
funcIndex int
|
||||||
|
ip int
|
||||||
|
opAddr int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *disassembler) disassemble(prefix string) error {
|
||||||
|
if prefix != "" {
|
||||||
|
d.writef(" // %s\n", prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
for d.ip < len(d.code) && d.err == nil {
|
||||||
|
d.opAddr = d.ip
|
||||||
|
op := d.fetch()
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case Num:
|
||||||
|
index := d.fetch()
|
||||||
|
num := d.program.Nums[index]
|
||||||
|
if num == float64(int(num)) {
|
||||||
|
d.writeOpf("Num %d (%d)", int(num), index)
|
||||||
|
} else {
|
||||||
|
d.writeOpf("Num %.6g (%d)", num, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
case Str:
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("Str %q (%d)", d.program.Strs[index], index)
|
||||||
|
|
||||||
|
case FieldInt:
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("FieldInt %d", index)
|
||||||
|
|
||||||
|
case FieldByNameStr:
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("FieldByNameStr %q (%d)", d.program.Strs[index], index)
|
||||||
|
|
||||||
|
case Global:
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("Global %s", d.program.scalarNames[index])
|
||||||
|
|
||||||
|
case Local:
|
||||||
|
index := int(d.fetch())
|
||||||
|
d.writeOpf("Local %s", d.localName(index))
|
||||||
|
|
||||||
|
case Special:
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("Special %s", ast.SpecialVarName(int(index)))
|
||||||
|
|
||||||
|
case ArrayGlobal:
|
||||||
|
arrayIndex := d.fetch()
|
||||||
|
d.writeOpf("ArrayGlobal %s", d.program.arrayNames[arrayIndex])
|
||||||
|
|
||||||
|
case ArrayLocal:
|
||||||
|
arrayIndex := d.fetch()
|
||||||
|
d.writeOpf("ArrayLocal %s", d.localArrayName(int(arrayIndex)))
|
||||||
|
|
||||||
|
case InGlobal:
|
||||||
|
arrayIndex := d.fetch()
|
||||||
|
d.writeOpf("InGlobal %s", d.program.arrayNames[arrayIndex])
|
||||||
|
|
||||||
|
case InLocal:
|
||||||
|
arrayIndex := int(d.fetch())
|
||||||
|
d.writeOpf("InLocal %s", d.localArrayName(arrayIndex))
|
||||||
|
|
||||||
|
case AssignGlobal:
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("AssignGlobal %s", d.program.scalarNames[index])
|
||||||
|
|
||||||
|
case AssignLocal:
|
||||||
|
index := int(d.fetch())
|
||||||
|
d.writeOpf("AssignLocal %s", d.localName(index))
|
||||||
|
|
||||||
|
case AssignSpecial:
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("AssignSpecial %s", ast.SpecialVarName(int(index)))
|
||||||
|
|
||||||
|
case AssignArrayGlobal:
|
||||||
|
arrayIndex := d.fetch()
|
||||||
|
d.writeOpf("AssignArrayGlobal %s", d.program.arrayNames[arrayIndex])
|
||||||
|
|
||||||
|
case AssignArrayLocal:
|
||||||
|
arrayIndex := int(d.fetch())
|
||||||
|
d.writeOpf("AssignArrayLocal %s", d.localArrayName(arrayIndex))
|
||||||
|
|
||||||
|
case Delete:
|
||||||
|
arrayScope := ast.VarScope(d.fetch())
|
||||||
|
arrayIndex := int(d.fetch())
|
||||||
|
d.writeOpf("Delete %s", d.arrayName(arrayScope, arrayIndex))
|
||||||
|
|
||||||
|
case DeleteAll:
|
||||||
|
arrayScope := ast.VarScope(d.fetch())
|
||||||
|
arrayIndex := int(d.fetch())
|
||||||
|
d.writeOpf("DeleteAll %s", d.arrayName(arrayScope, arrayIndex))
|
||||||
|
|
||||||
|
case IncrField:
|
||||||
|
amount := d.fetch()
|
||||||
|
d.writeOpf("IncrField %d", amount)
|
||||||
|
|
||||||
|
case IncrGlobal:
|
||||||
|
amount := d.fetch()
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("IncrGlobal %d %s", amount, d.program.scalarNames[index])
|
||||||
|
|
||||||
|
case IncrLocal:
|
||||||
|
amount := d.fetch()
|
||||||
|
index := int(d.fetch())
|
||||||
|
d.writeOpf("IncrLocal %d %s", amount, d.localName(index))
|
||||||
|
|
||||||
|
case IncrSpecial:
|
||||||
|
amount := d.fetch()
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("IncrSpecial %d %s", amount, ast.SpecialVarName(int(index)))
|
||||||
|
|
||||||
|
case IncrArrayGlobal:
|
||||||
|
amount := d.fetch()
|
||||||
|
arrayIndex := d.fetch()
|
||||||
|
d.writeOpf("IncrArrayGlobal %d %s", amount, d.program.arrayNames[arrayIndex])
|
||||||
|
|
||||||
|
case IncrArrayLocal:
|
||||||
|
amount := d.fetch()
|
||||||
|
arrayIndex := int(d.fetch())
|
||||||
|
d.writeOpf("IncrArrayLocal %d %s", amount, d.localArrayName(arrayIndex))
|
||||||
|
|
||||||
|
case AugAssignField:
|
||||||
|
operation := AugOp(d.fetch())
|
||||||
|
d.writeOpf("AugAssignField %s", operation)
|
||||||
|
|
||||||
|
case AugAssignGlobal:
|
||||||
|
operation := AugOp(d.fetch())
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("AugAssignGlobal %s %s", operation, d.program.scalarNames[index])
|
||||||
|
|
||||||
|
case AugAssignLocal:
|
||||||
|
operation := AugOp(d.fetch())
|
||||||
|
index := int(d.fetch())
|
||||||
|
d.writeOpf("AugAssignLocal %s %s", operation, d.localName(index))
|
||||||
|
|
||||||
|
case AugAssignSpecial:
|
||||||
|
operation := AugOp(d.fetch())
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("AugAssignSpecial %s %d", operation, ast.SpecialVarName(int(index)))
|
||||||
|
|
||||||
|
case AugAssignArrayGlobal:
|
||||||
|
operation := AugOp(d.fetch())
|
||||||
|
arrayIndex := d.fetch()
|
||||||
|
d.writeOpf("AugAssignArrayGlobal %s %s", operation, d.program.arrayNames[arrayIndex])
|
||||||
|
|
||||||
|
case AugAssignArrayLocal:
|
||||||
|
operation := AugOp(d.fetch())
|
||||||
|
arrayIndex := int(d.fetch())
|
||||||
|
d.writeOpf("AugAssignArrayLocal %s %s", operation, d.localArrayName(arrayIndex))
|
||||||
|
|
||||||
|
case Regex:
|
||||||
|
regexIndex := d.fetch()
|
||||||
|
d.writeOpf("Regex %q (%d)", d.program.Regexes[regexIndex], regexIndex)
|
||||||
|
|
||||||
|
case IndexMulti:
|
||||||
|
num := d.fetch()
|
||||||
|
d.writeOpf("IndexMulti %d", num)
|
||||||
|
|
||||||
|
case ConcatMulti:
|
||||||
|
num := d.fetch()
|
||||||
|
d.writeOpf("ConcatMulti %d", num)
|
||||||
|
|
||||||
|
case Jump:
|
||||||
|
offset := d.fetch()
|
||||||
|
d.writeOpf("Jump 0x%04x", d.ip+int(offset))
|
||||||
|
|
||||||
|
case JumpFalse:
|
||||||
|
offset := d.fetch()
|
||||||
|
d.writeOpf("JumpFalse 0x%04x", d.ip+int(offset))
|
||||||
|
|
||||||
|
case JumpTrue:
|
||||||
|
offset := d.fetch()
|
||||||
|
d.writeOpf("JumpTrue 0x%04x", d.ip+int(offset))
|
||||||
|
|
||||||
|
case JumpEquals:
|
||||||
|
offset := d.fetch()
|
||||||
|
d.writeOpf("JumpEquals 0x%04x", d.ip+int(offset))
|
||||||
|
|
||||||
|
case JumpNotEquals:
|
||||||
|
offset := d.fetch()
|
||||||
|
d.writeOpf("JumpNotEquals 0x%04x", d.ip+int(offset))
|
||||||
|
|
||||||
|
case JumpLess:
|
||||||
|
offset := d.fetch()
|
||||||
|
d.writeOpf("JumpLess 0x%04x", d.ip+int(offset))
|
||||||
|
|
||||||
|
case JumpGreater:
|
||||||
|
offset := d.fetch()
|
||||||
|
d.writeOpf("JumpGreater 0x%04x", d.ip+int(offset))
|
||||||
|
|
||||||
|
case JumpLessOrEqual:
|
||||||
|
offset := d.fetch()
|
||||||
|
d.writeOpf("JumpLessOrEqual 0x%04x", d.ip+int(offset))
|
||||||
|
|
||||||
|
case JumpGreaterOrEqual:
|
||||||
|
offset := d.fetch()
|
||||||
|
d.writeOpf("JumpGreaterOrEqual 0x%04x", d.ip+int(offset))
|
||||||
|
|
||||||
|
case ForIn:
|
||||||
|
varScope := ast.VarScope(d.fetch())
|
||||||
|
varIndex := int(d.fetch())
|
||||||
|
arrayScope := ast.VarScope(d.fetch())
|
||||||
|
arrayIndex := int(d.fetch())
|
||||||
|
offset := d.fetch()
|
||||||
|
d.writeOpf("ForIn %s %s 0x%04x", d.varName(varScope, varIndex), d.arrayName(arrayScope, arrayIndex), d.ip+int(offset))
|
||||||
|
|
||||||
|
case CallBuiltin:
|
||||||
|
builtinOp := BuiltinOp(d.fetch())
|
||||||
|
d.writeOpf("CallBuiltin %s", builtinOp)
|
||||||
|
|
||||||
|
case CallSplit:
|
||||||
|
arrayScope := ast.VarScope(d.fetch())
|
||||||
|
arrayIndex := int(d.fetch())
|
||||||
|
d.writeOpf("CallSplit %s", d.arrayName(arrayScope, arrayIndex))
|
||||||
|
|
||||||
|
case CallSplitSep:
|
||||||
|
arrayScope := ast.VarScope(d.fetch())
|
||||||
|
arrayIndex := int(d.fetch())
|
||||||
|
d.writeOpf("CallSplitSep %s", d.arrayName(arrayScope, arrayIndex))
|
||||||
|
|
||||||
|
case CallSprintf:
|
||||||
|
numArgs := d.fetch()
|
||||||
|
d.writeOpf("CallSprintf %d", numArgs)
|
||||||
|
|
||||||
|
case CallUser:
|
||||||
|
funcIndex := d.fetch()
|
||||||
|
numArrayArgs := int(d.fetch())
|
||||||
|
var arrayArgs []string
|
||||||
|
for i := 0; i < numArrayArgs; i++ {
|
||||||
|
arrayScope := ast.VarScope(d.fetch())
|
||||||
|
arrayIndex := int(d.fetch())
|
||||||
|
arrayArgs = append(arrayArgs, d.arrayName(arrayScope, arrayIndex))
|
||||||
|
}
|
||||||
|
d.writeOpf("CallUser %s [%s]", d.program.Functions[funcIndex].Name, strings.Join(arrayArgs, ", "))
|
||||||
|
|
||||||
|
case CallNative:
|
||||||
|
funcIndex := d.fetch()
|
||||||
|
numArgs := d.fetch()
|
||||||
|
d.writeOpf("CallNative %s %d", d.nativeFuncNames[funcIndex], numArgs)
|
||||||
|
|
||||||
|
case Nulls:
|
||||||
|
numNulls := d.fetch()
|
||||||
|
d.writeOpf("Nulls %d", numNulls)
|
||||||
|
|
||||||
|
case Print:
|
||||||
|
numArgs := d.fetch()
|
||||||
|
redirect := lexer.Token(d.fetch())
|
||||||
|
if redirect == lexer.ILLEGAL {
|
||||||
|
d.writeOpf("Print %d", numArgs)
|
||||||
|
} else {
|
||||||
|
d.writeOpf("Print %d %s", numArgs, redirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
case Printf:
|
||||||
|
numArgs := d.fetch()
|
||||||
|
redirect := lexer.Token(d.fetch())
|
||||||
|
if redirect == lexer.ILLEGAL {
|
||||||
|
d.writeOpf("Printf %d", numArgs)
|
||||||
|
} else {
|
||||||
|
d.writeOpf("Printf %d %s", numArgs, redirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
case Getline:
|
||||||
|
redirect := lexer.Token(d.fetch())
|
||||||
|
d.writeOpf("Getline %s", redirect)
|
||||||
|
|
||||||
|
case GetlineField:
|
||||||
|
redirect := lexer.Token(d.fetch())
|
||||||
|
d.writeOpf("GetlineField %s", redirect)
|
||||||
|
|
||||||
|
case GetlineGlobal:
|
||||||
|
redirect := lexer.Token(d.fetch())
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("GetlineGlobal %s %s", redirect, d.program.scalarNames[index])
|
||||||
|
|
||||||
|
case GetlineLocal:
|
||||||
|
redirect := lexer.Token(d.fetch())
|
||||||
|
index := int(d.fetch())
|
||||||
|
d.writeOpf("GetlineLocal %s %s", redirect, d.localName(index))
|
||||||
|
|
||||||
|
case GetlineSpecial:
|
||||||
|
redirect := lexer.Token(d.fetch())
|
||||||
|
index := d.fetch()
|
||||||
|
d.writeOpf("GetlineSpecial %s %s", redirect, ast.SpecialVarName(int(index)))
|
||||||
|
|
||||||
|
case GetlineArray:
|
||||||
|
redirect := lexer.Token(d.fetch())
|
||||||
|
arrayScope := ast.VarScope(d.fetch())
|
||||||
|
arrayIndex := int(d.fetch())
|
||||||
|
d.writeOpf("GetlineArray %s %s", redirect, d.arrayName(arrayScope, arrayIndex))
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Handles all other opcodes with no arguments
|
||||||
|
d.writeOpf("%s", op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.writef("\n")
|
||||||
|
return d.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the next opcode and increment the "instruction pointer".
|
||||||
|
func (d *disassembler) fetch() Opcode {
|
||||||
|
op := d.code[d.ip]
|
||||||
|
d.ip++
|
||||||
|
return op
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write formatted string to the disassembly output.
|
||||||
|
func (d *disassembler) writef(format string, args ...interface{}) {
|
||||||
|
if d.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, d.err = fmt.Fprintf(d.writer, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write formatted opcode (with address and newline) to disassembly output.
|
||||||
|
func (d *disassembler) writeOpf(format string, args ...interface{}) {
|
||||||
|
if d.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addrStr := fmt.Sprintf("%04x", d.opAddr)
|
||||||
|
_, d.err = fmt.Fprintf(d.writer, addrStr+" "+format+"\n", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the scalar variable name described by scope and index.
|
||||||
|
func (d *disassembler) varName(scope ast.VarScope, index int) string {
|
||||||
|
switch scope {
|
||||||
|
case ast.ScopeGlobal:
|
||||||
|
return d.program.scalarNames[index]
|
||||||
|
case ast.ScopeLocal:
|
||||||
|
return d.localName(index)
|
||||||
|
default: // ScopeSpecial
|
||||||
|
return ast.SpecialVarName(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the local variable name with the given index.
|
||||||
|
func (d *disassembler) localName(index int) string {
|
||||||
|
f := d.program.Functions[d.funcIndex]
|
||||||
|
n := 0
|
||||||
|
for i, p := range f.Params {
|
||||||
|
if f.Arrays[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n == index {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("unexpected local variable index %d", index))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the array variable name describes by scope and index.
|
||||||
|
func (d *disassembler) arrayName(scope ast.VarScope, index int) string {
|
||||||
|
if scope == ast.ScopeLocal {
|
||||||
|
return d.localArrayName(index)
|
||||||
|
}
|
||||||
|
return d.program.arrayNames[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the local array name with the given index.
|
||||||
|
func (d *disassembler) localArrayName(index int) string {
|
||||||
|
f := d.program.Functions[d.funcIndex]
|
||||||
|
n := 0
|
||||||
|
for i, p := range f.Params {
|
||||||
|
if !f.Arrays[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n == index {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("unexpected local array index %d", index))
|
||||||
|
}
|
174
play/vendor/github.com/benhoyt/goawk/internal/compiler/opcode_string.go
generated
vendored
Normal file
174
play/vendor/github.com/benhoyt/goawk/internal/compiler/opcode_string.go
generated
vendored
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
// Code generated by "stringer -type=Opcode,AugOp,BuiltinOp"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package compiler
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[Nop-0]
|
||||||
|
_ = x[Num-1]
|
||||||
|
_ = x[Str-2]
|
||||||
|
_ = x[Dupe-3]
|
||||||
|
_ = x[Drop-4]
|
||||||
|
_ = x[Swap-5]
|
||||||
|
_ = x[Field-6]
|
||||||
|
_ = x[FieldInt-7]
|
||||||
|
_ = x[FieldByName-8]
|
||||||
|
_ = x[FieldByNameStr-9]
|
||||||
|
_ = x[Global-10]
|
||||||
|
_ = x[Local-11]
|
||||||
|
_ = x[Special-12]
|
||||||
|
_ = x[ArrayGlobal-13]
|
||||||
|
_ = x[ArrayLocal-14]
|
||||||
|
_ = x[InGlobal-15]
|
||||||
|
_ = x[InLocal-16]
|
||||||
|
_ = x[AssignField-17]
|
||||||
|
_ = x[AssignGlobal-18]
|
||||||
|
_ = x[AssignLocal-19]
|
||||||
|
_ = x[AssignSpecial-20]
|
||||||
|
_ = x[AssignArrayGlobal-21]
|
||||||
|
_ = x[AssignArrayLocal-22]
|
||||||
|
_ = x[Delete-23]
|
||||||
|
_ = x[DeleteAll-24]
|
||||||
|
_ = x[IncrField-25]
|
||||||
|
_ = x[IncrGlobal-26]
|
||||||
|
_ = x[IncrLocal-27]
|
||||||
|
_ = x[IncrSpecial-28]
|
||||||
|
_ = x[IncrArrayGlobal-29]
|
||||||
|
_ = x[IncrArrayLocal-30]
|
||||||
|
_ = x[AugAssignField-31]
|
||||||
|
_ = x[AugAssignGlobal-32]
|
||||||
|
_ = x[AugAssignLocal-33]
|
||||||
|
_ = x[AugAssignSpecial-34]
|
||||||
|
_ = x[AugAssignArrayGlobal-35]
|
||||||
|
_ = x[AugAssignArrayLocal-36]
|
||||||
|
_ = x[Regex-37]
|
||||||
|
_ = x[IndexMulti-38]
|
||||||
|
_ = x[ConcatMulti-39]
|
||||||
|
_ = x[Add-40]
|
||||||
|
_ = x[Subtract-41]
|
||||||
|
_ = x[Multiply-42]
|
||||||
|
_ = x[Divide-43]
|
||||||
|
_ = x[Power-44]
|
||||||
|
_ = x[Modulo-45]
|
||||||
|
_ = x[Equals-46]
|
||||||
|
_ = x[NotEquals-47]
|
||||||
|
_ = x[Less-48]
|
||||||
|
_ = x[Greater-49]
|
||||||
|
_ = x[LessOrEqual-50]
|
||||||
|
_ = x[GreaterOrEqual-51]
|
||||||
|
_ = x[Concat-52]
|
||||||
|
_ = x[Match-53]
|
||||||
|
_ = x[NotMatch-54]
|
||||||
|
_ = x[Not-55]
|
||||||
|
_ = x[UnaryMinus-56]
|
||||||
|
_ = x[UnaryPlus-57]
|
||||||
|
_ = x[Boolean-58]
|
||||||
|
_ = x[Jump-59]
|
||||||
|
_ = x[JumpFalse-60]
|
||||||
|
_ = x[JumpTrue-61]
|
||||||
|
_ = x[JumpEquals-62]
|
||||||
|
_ = x[JumpNotEquals-63]
|
||||||
|
_ = x[JumpLess-64]
|
||||||
|
_ = x[JumpGreater-65]
|
||||||
|
_ = x[JumpLessOrEqual-66]
|
||||||
|
_ = x[JumpGreaterOrEqual-67]
|
||||||
|
_ = x[Next-68]
|
||||||
|
_ = x[Exit-69]
|
||||||
|
_ = x[ForIn-70]
|
||||||
|
_ = x[BreakForIn-71]
|
||||||
|
_ = x[CallBuiltin-72]
|
||||||
|
_ = x[CallSplit-73]
|
||||||
|
_ = x[CallSplitSep-74]
|
||||||
|
_ = x[CallSprintf-75]
|
||||||
|
_ = x[CallUser-76]
|
||||||
|
_ = x[CallNative-77]
|
||||||
|
_ = x[Return-78]
|
||||||
|
_ = x[ReturnNull-79]
|
||||||
|
_ = x[Nulls-80]
|
||||||
|
_ = x[Print-81]
|
||||||
|
_ = x[Printf-82]
|
||||||
|
_ = x[Getline-83]
|
||||||
|
_ = x[GetlineField-84]
|
||||||
|
_ = x[GetlineGlobal-85]
|
||||||
|
_ = x[GetlineLocal-86]
|
||||||
|
_ = x[GetlineSpecial-87]
|
||||||
|
_ = x[GetlineArray-88]
|
||||||
|
_ = x[EndOpcode-89]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Opcode_name = "NopNumStrDupeDropSwapFieldFieldIntFieldByNameFieldByNameStrGlobalLocalSpecialArrayGlobalArrayLocalInGlobalInLocalAssignFieldAssignGlobalAssignLocalAssignSpecialAssignArrayGlobalAssignArrayLocalDeleteDeleteAllIncrFieldIncrGlobalIncrLocalIncrSpecialIncrArrayGlobalIncrArrayLocalAugAssignFieldAugAssignGlobalAugAssignLocalAugAssignSpecialAugAssignArrayGlobalAugAssignArrayLocalRegexIndexMultiConcatMultiAddSubtractMultiplyDividePowerModuloEqualsNotEqualsLessGreaterLessOrEqualGreaterOrEqualConcatMatchNotMatchNotUnaryMinusUnaryPlusBooleanJumpJumpFalseJumpTrueJumpEqualsJumpNotEqualsJumpLessJumpGreaterJumpLessOrEqualJumpGreaterOrEqualNextExitForInBreakForInCallBuiltinCallSplitCallSplitSepCallSprintfCallUserCallNativeReturnReturnNullNullsPrintPrintfGetlineGetlineFieldGetlineGlobalGetlineLocalGetlineSpecialGetlineArrayEndOpcode"
|
||||||
|
|
||||||
|
var _Opcode_index = [...]uint16{0, 3, 6, 9, 13, 17, 21, 26, 34, 45, 59, 65, 70, 77, 88, 98, 106, 113, 124, 136, 147, 160, 177, 193, 199, 208, 217, 227, 236, 247, 262, 276, 290, 305, 319, 335, 355, 374, 379, 389, 400, 403, 411, 419, 425, 430, 436, 442, 451, 455, 462, 473, 487, 493, 498, 506, 509, 519, 528, 535, 539, 548, 556, 566, 579, 587, 598, 613, 631, 635, 639, 644, 654, 665, 674, 686, 697, 705, 715, 721, 731, 736, 741, 747, 754, 766, 779, 791, 805, 817, 826}
|
||||||
|
|
||||||
|
func (i Opcode) String() string {
|
||||||
|
if i < 0 || i >= Opcode(len(_Opcode_index)-1) {
|
||||||
|
return "Opcode(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _Opcode_name[_Opcode_index[i]:_Opcode_index[i+1]]
|
||||||
|
}
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[AugOpAdd-0]
|
||||||
|
_ = x[AugOpSub-1]
|
||||||
|
_ = x[AugOpMul-2]
|
||||||
|
_ = x[AugOpDiv-3]
|
||||||
|
_ = x[AugOpPow-4]
|
||||||
|
_ = x[AugOpMod-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _AugOp_name = "AugOpAddAugOpSubAugOpMulAugOpDivAugOpPowAugOpMod"
|
||||||
|
|
||||||
|
var _AugOp_index = [...]uint8{0, 8, 16, 24, 32, 40, 48}
|
||||||
|
|
||||||
|
func (i AugOp) String() string {
|
||||||
|
if i < 0 || i >= AugOp(len(_AugOp_index)-1) {
|
||||||
|
return "AugOp(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _AugOp_name[_AugOp_index[i]:_AugOp_index[i+1]]
|
||||||
|
}
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[BuiltinAtan2-0]
|
||||||
|
_ = x[BuiltinClose-1]
|
||||||
|
_ = x[BuiltinCos-2]
|
||||||
|
_ = x[BuiltinExp-3]
|
||||||
|
_ = x[BuiltinFflush-4]
|
||||||
|
_ = x[BuiltinFflushAll-5]
|
||||||
|
_ = x[BuiltinGsub-6]
|
||||||
|
_ = x[BuiltinIndex-7]
|
||||||
|
_ = x[BuiltinInt-8]
|
||||||
|
_ = x[BuiltinLength-9]
|
||||||
|
_ = x[BuiltinLengthArg-10]
|
||||||
|
_ = x[BuiltinLog-11]
|
||||||
|
_ = x[BuiltinMatch-12]
|
||||||
|
_ = x[BuiltinRand-13]
|
||||||
|
_ = x[BuiltinSin-14]
|
||||||
|
_ = x[BuiltinSqrt-15]
|
||||||
|
_ = x[BuiltinSrand-16]
|
||||||
|
_ = x[BuiltinSrandSeed-17]
|
||||||
|
_ = x[BuiltinSub-18]
|
||||||
|
_ = x[BuiltinSubstr-19]
|
||||||
|
_ = x[BuiltinSubstrLength-20]
|
||||||
|
_ = x[BuiltinSystem-21]
|
||||||
|
_ = x[BuiltinTolower-22]
|
||||||
|
_ = x[BuiltinToupper-23]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _BuiltinOp_name = "BuiltinAtan2BuiltinCloseBuiltinCosBuiltinExpBuiltinFflushBuiltinFflushAllBuiltinGsubBuiltinIndexBuiltinIntBuiltinLengthBuiltinLengthArgBuiltinLogBuiltinMatchBuiltinRandBuiltinSinBuiltinSqrtBuiltinSrandBuiltinSrandSeedBuiltinSubBuiltinSubstrBuiltinSubstrLengthBuiltinSystemBuiltinTolowerBuiltinToupper"
|
||||||
|
|
||||||
|
var _BuiltinOp_index = [...]uint16{0, 12, 24, 34, 44, 57, 73, 84, 96, 106, 119, 135, 145, 157, 168, 178, 189, 201, 217, 227, 240, 259, 272, 286, 300}
|
||||||
|
|
||||||
|
func (i BuiltinOp) String() string {
|
||||||
|
if i < 0 || i >= BuiltinOp(len(_BuiltinOp_index)-1) {
|
||||||
|
return "BuiltinOp(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _BuiltinOp_name[_BuiltinOp_index[i]:_BuiltinOp_index[i+1]]
|
||||||
|
}
|
180
play/vendor/github.com/benhoyt/goawk/internal/compiler/opcodes.go
generated
vendored
Normal file
180
play/vendor/github.com/benhoyt/goawk/internal/compiler/opcodes.go
generated
vendored
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
package compiler
|
||||||
|
|
||||||
|
//go:generate go run golang.org/x/tools/cmd/[email protected] -type=Opcode,AugOp,BuiltinOp
|
||||||
|
|
||||||
|
// Opcode represents a single virtual machine instruction (or argument). The
|
||||||
|
// comments beside each opcode show any arguments that instruction consumes.
|
||||||
|
//
|
||||||
|
// Normally this is called "bytecode", but I've avoided that term here as each
|
||||||
|
// opcode is a 32-bit word, not an 8-bit byte.
|
||||||
|
//
|
||||||
|
// I tested various bit widths, and I believe 32 bit was the fastest, but also
|
||||||
|
// means we don't have to worry about jump offsets overflowing. That's tested
|
||||||
|
// in the compiler, but who's going to have an AWK program bigger than 2GB?
|
||||||
|
type Opcode int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Nop Opcode = iota
|
||||||
|
|
||||||
|
// Stack operations
|
||||||
|
Num // numIndex
|
||||||
|
Str // strIndex
|
||||||
|
Dupe
|
||||||
|
Drop
|
||||||
|
Swap
|
||||||
|
|
||||||
|
// Fetch a field, variable, or array item
|
||||||
|
Field
|
||||||
|
FieldInt // index
|
||||||
|
FieldByName
|
||||||
|
FieldByNameStr // strIndex
|
||||||
|
Global // index
|
||||||
|
Local // index
|
||||||
|
Special // index
|
||||||
|
ArrayGlobal // arrayIndex
|
||||||
|
ArrayLocal // arrayIndex
|
||||||
|
InGlobal // arrayIndex
|
||||||
|
InLocal // arrayIndex
|
||||||
|
|
||||||
|
// Assign a field, variable, or array item
|
||||||
|
AssignField
|
||||||
|
AssignGlobal // index
|
||||||
|
AssignLocal // index
|
||||||
|
AssignSpecial // index
|
||||||
|
AssignArrayGlobal // arrayIndex
|
||||||
|
AssignArrayLocal // arrayIndex
|
||||||
|
|
||||||
|
// Delete statement
|
||||||
|
Delete // arrayScope arrayIndex
|
||||||
|
DeleteAll // arrayScope arrayIndex
|
||||||
|
|
||||||
|
// Post-increment and post-decrement
|
||||||
|
IncrField // amount
|
||||||
|
IncrGlobal // amount index
|
||||||
|
IncrLocal // amount index
|
||||||
|
IncrSpecial // amount index
|
||||||
|
IncrArrayGlobal // amount arrayIndex
|
||||||
|
IncrArrayLocal // amount arrayIndex
|
||||||
|
|
||||||
|
// Augmented assignment (also used for pre-increment and pre-decrement)
|
||||||
|
AugAssignField // augOp
|
||||||
|
AugAssignGlobal // augOp index
|
||||||
|
AugAssignLocal // augOp index
|
||||||
|
AugAssignSpecial // augOp index
|
||||||
|
AugAssignArrayGlobal // augOp arrayIndex
|
||||||
|
AugAssignArrayLocal // augOp arrayIndex
|
||||||
|
|
||||||
|
// Stand-alone regex expression /foo/
|
||||||
|
Regex // regexIndex
|
||||||
|
|
||||||
|
// Multi-index concatenation
|
||||||
|
IndexMulti // num
|
||||||
|
|
||||||
|
// Multi-value concatenation
|
||||||
|
ConcatMulti // num
|
||||||
|
|
||||||
|
// Binary operators
|
||||||
|
Add
|
||||||
|
Subtract
|
||||||
|
Multiply
|
||||||
|
Divide
|
||||||
|
Power
|
||||||
|
Modulo
|
||||||
|
Equals
|
||||||
|
NotEquals
|
||||||
|
Less
|
||||||
|
Greater
|
||||||
|
LessOrEqual
|
||||||
|
GreaterOrEqual
|
||||||
|
Concat
|
||||||
|
Match
|
||||||
|
NotMatch
|
||||||
|
|
||||||
|
// Unary operators
|
||||||
|
Not
|
||||||
|
UnaryMinus
|
||||||
|
UnaryPlus
|
||||||
|
Boolean
|
||||||
|
|
||||||
|
// Control flow
|
||||||
|
Jump // offset
|
||||||
|
JumpFalse // offset
|
||||||
|
JumpTrue // offset
|
||||||
|
JumpEquals // offset
|
||||||
|
JumpNotEquals // offset
|
||||||
|
JumpLess // offset
|
||||||
|
JumpGreater // offset
|
||||||
|
JumpLessOrEqual // offset
|
||||||
|
JumpGreaterOrEqual // offset
|
||||||
|
Next
|
||||||
|
Exit
|
||||||
|
ForIn // varScope varIndex arrayScope arrayIndex offset
|
||||||
|
BreakForIn
|
||||||
|
|
||||||
|
// Builtin functions
|
||||||
|
CallBuiltin // builtinOp
|
||||||
|
CallSplit // arrayScope arrayIndex
|
||||||
|
CallSplitSep // arrayScope arrayIndex
|
||||||
|
CallSprintf // numArgs
|
||||||
|
|
||||||
|
// User and native functions
|
||||||
|
CallUser // funcIndex numArrayArgs [arrayScope1 arrayIndex1 ...]
|
||||||
|
CallNative // funcIndex numArgs
|
||||||
|
Return
|
||||||
|
ReturnNull
|
||||||
|
Nulls // numNulls
|
||||||
|
|
||||||
|
// Print, printf, and getline
|
||||||
|
Print // numArgs redirect
|
||||||
|
Printf // numArgs redirect
|
||||||
|
Getline // redirect
|
||||||
|
GetlineField // redirect
|
||||||
|
GetlineGlobal // redirect index
|
||||||
|
GetlineLocal // redirect index
|
||||||
|
GetlineSpecial // redirect index
|
||||||
|
GetlineArray // redirect arrayScope arrayIndex
|
||||||
|
|
||||||
|
EndOpcode
|
||||||
|
)
|
||||||
|
|
||||||
|
// AugOp represents an augmented assignment operation.
|
||||||
|
type AugOp Opcode
|
||||||
|
|
||||||
|
const (
|
||||||
|
AugOpAdd AugOp = iota
|
||||||
|
AugOpSub
|
||||||
|
AugOpMul
|
||||||
|
AugOpDiv
|
||||||
|
AugOpPow
|
||||||
|
AugOpMod
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuiltinOp represents a builtin function call.
|
||||||
|
type BuiltinOp Opcode
|
||||||
|
|
||||||
|
const (
|
||||||
|
BuiltinAtan2 BuiltinOp = iota
|
||||||
|
BuiltinClose
|
||||||
|
BuiltinCos
|
||||||
|
BuiltinExp
|
||||||
|
BuiltinFflush
|
||||||
|
BuiltinFflushAll
|
||||||
|
BuiltinGsub
|
||||||
|
BuiltinIndex
|
||||||
|
BuiltinInt
|
||||||
|
BuiltinLength
|
||||||
|
BuiltinLengthArg
|
||||||
|
BuiltinLog
|
||||||
|
BuiltinMatch
|
||||||
|
BuiltinRand
|
||||||
|
BuiltinSin
|
||||||
|
BuiltinSqrt
|
||||||
|
BuiltinSrand
|
||||||
|
BuiltinSrandSeed
|
||||||
|
BuiltinSub
|
||||||
|
BuiltinSubstr
|
||||||
|
BuiltinSubstrLength
|
||||||
|
BuiltinSystem
|
||||||
|
BuiltinTolower
|
||||||
|
BuiltinToupper
|
||||||
|
)
|
|
@ -1,4 +1,4 @@
|
||||||
// Evaluate builtin and user-defined function calls
|
// Call native Go functions; helpers for some builtin function calls.
|
||||||
|
|
||||||
package interp
|
package interp
|
||||||
|
|
||||||
|
@ -6,391 +6,19 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"os/exec"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
. "github.com/benhoyt/goawk/internal/ast"
|
"github.com/benhoyt/goawk/internal/ast"
|
||||||
. "github.com/benhoyt/goawk/lexer"
|
. "github.com/benhoyt/goawk/lexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Call builtin function specified by "op" with given args
|
|
||||||
func (p *interp) callBuiltin(op Token, argExprs []Expr) (value, error) {
|
|
||||||
// split() has an array arg (not evaluated) and [g]sub() have an
|
|
||||||
// lvalue arg, so handle them as special cases
|
|
||||||
switch op {
|
|
||||||
case F_SPLIT:
|
|
||||||
strValue, err := p.eval(argExprs[0])
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
str := p.toString(strValue)
|
|
||||||
var fieldSep string
|
|
||||||
if len(argExprs) == 3 {
|
|
||||||
sepValue, err := p.eval(argExprs[2])
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
fieldSep = p.toString(sepValue)
|
|
||||||
} else {
|
|
||||||
fieldSep = p.fieldSep
|
|
||||||
}
|
|
||||||
arrayExpr := argExprs[1].(*ArrayExpr)
|
|
||||||
n, err := p.split(str, arrayExpr.Scope, arrayExpr.Index, fieldSep)
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
return num(float64(n)), nil
|
|
||||||
|
|
||||||
case F_SUB, F_GSUB:
|
|
||||||
regexValue, err := p.eval(argExprs[0])
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
regex := p.toString(regexValue)
|
|
||||||
replValue, err := p.eval(argExprs[1])
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
repl := p.toString(replValue)
|
|
||||||
var in string
|
|
||||||
if len(argExprs) == 3 {
|
|
||||||
inValue, err := p.eval(argExprs[2])
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
in = p.toString(inValue)
|
|
||||||
} else {
|
|
||||||
in = p.line
|
|
||||||
}
|
|
||||||
out, n, err := p.sub(regex, repl, in, op == F_GSUB)
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
if len(argExprs) == 3 {
|
|
||||||
err := p.assign(argExprs[2], str(out))
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.setLine(out, true)
|
|
||||||
}
|
|
||||||
return num(float64(n)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now evaluate the argExprs (calls with up to 7 args don't
|
|
||||||
// require heap allocation)
|
|
||||||
args := make([]value, 0, 7)
|
|
||||||
for _, a := range argExprs {
|
|
||||||
arg, err := p.eval(a)
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
args = append(args, arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then switch on the function for the ordinary functions
|
|
||||||
switch op {
|
|
||||||
case F_LENGTH:
|
|
||||||
var s string
|
|
||||||
if len(args) > 0 {
|
|
||||||
s = p.toString(args[0])
|
|
||||||
} else {
|
|
||||||
s = p.line
|
|
||||||
}
|
|
||||||
var n int
|
|
||||||
if p.bytes {
|
|
||||||
n = len(s)
|
|
||||||
} else {
|
|
||||||
n = utf8.RuneCountInString(s)
|
|
||||||
}
|
|
||||||
return num(float64(n)), nil
|
|
||||||
|
|
||||||
case F_MATCH:
|
|
||||||
re, err := p.compileRegex(p.toString(args[1]))
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
s := p.toString(args[0])
|
|
||||||
loc := re.FindStringIndex(s)
|
|
||||||
if loc == nil {
|
|
||||||
p.matchStart = 0
|
|
||||||
p.matchLength = -1
|
|
||||||
return num(0), nil
|
|
||||||
}
|
|
||||||
if p.bytes {
|
|
||||||
p.matchStart = loc[0] + 1
|
|
||||||
p.matchLength = loc[1] - loc[0]
|
|
||||||
} else {
|
|
||||||
p.matchStart = utf8.RuneCountInString(s[:loc[0]]) + 1
|
|
||||||
p.matchLength = utf8.RuneCountInString(s[loc[0]:loc[1]])
|
|
||||||
}
|
|
||||||
return num(float64(p.matchStart)), nil
|
|
||||||
|
|
||||||
case F_SUBSTR:
|
|
||||||
s := p.toString(args[0])
|
|
||||||
pos := int(args[1].num())
|
|
||||||
if p.bytes {
|
|
||||||
if pos > len(s) {
|
|
||||||
pos = len(s) + 1
|
|
||||||
}
|
|
||||||
if pos < 1 {
|
|
||||||
pos = 1
|
|
||||||
}
|
|
||||||
maxLength := len(s) - pos + 1
|
|
||||||
length := maxLength
|
|
||||||
if len(args) == 3 {
|
|
||||||
length = int(args[2].num())
|
|
||||||
if length < 0 {
|
|
||||||
length = 0
|
|
||||||
}
|
|
||||||
if length > maxLength {
|
|
||||||
length = maxLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return str(s[pos-1 : pos-1+length]), nil
|
|
||||||
} else {
|
|
||||||
// Count characters till we get to pos.
|
|
||||||
chars := 1
|
|
||||||
start := 0
|
|
||||||
for start = range s {
|
|
||||||
chars++
|
|
||||||
if chars > pos {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pos >= chars {
|
|
||||||
start = len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count characters from start till we reach length.
|
|
||||||
var end int
|
|
||||||
if len(args) == 3 {
|
|
||||||
length := int(args[2].num())
|
|
||||||
chars = 0
|
|
||||||
for end = range s[start:] {
|
|
||||||
chars++
|
|
||||||
if chars > length {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if length >= chars {
|
|
||||||
end = len(s)
|
|
||||||
} else {
|
|
||||||
end += start
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
end = len(s)
|
|
||||||
}
|
|
||||||
return str(s[start:end]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case F_SPRINTF:
|
|
||||||
s, err := p.sprintf(p.toString(args[0]), args[1:])
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
return str(s), nil
|
|
||||||
|
|
||||||
case F_INDEX:
|
|
||||||
s := p.toString(args[0])
|
|
||||||
substr := p.toString(args[1])
|
|
||||||
index := strings.Index(s, substr)
|
|
||||||
if p.bytes {
|
|
||||||
return num(float64(index + 1)), nil
|
|
||||||
} else {
|
|
||||||
if index < 0 {
|
|
||||||
return num(float64(0)), nil
|
|
||||||
}
|
|
||||||
index = utf8.RuneCountInString(s[:index])
|
|
||||||
return num(float64(index + 1)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case F_TOLOWER:
|
|
||||||
return str(strings.ToLower(p.toString(args[0]))), nil
|
|
||||||
case F_TOUPPER:
|
|
||||||
return str(strings.ToUpper(p.toString(args[0]))), nil
|
|
||||||
|
|
||||||
case F_ATAN2:
|
|
||||||
return num(math.Atan2(args[0].num(), args[1].num())), nil
|
|
||||||
case F_COS:
|
|
||||||
return num(math.Cos(args[0].num())), nil
|
|
||||||
case F_EXP:
|
|
||||||
return num(math.Exp(args[0].num())), nil
|
|
||||||
case F_INT:
|
|
||||||
return num(float64(int(args[0].num()))), nil
|
|
||||||
case F_LOG:
|
|
||||||
return num(math.Log(args[0].num())), nil
|
|
||||||
case F_SQRT:
|
|
||||||
return num(math.Sqrt(args[0].num())), nil
|
|
||||||
case F_RAND:
|
|
||||||
return num(p.random.Float64()), nil
|
|
||||||
case F_SIN:
|
|
||||||
return num(math.Sin(args[0].num())), nil
|
|
||||||
|
|
||||||
case F_SRAND:
|
|
||||||
prevSeed := p.randSeed
|
|
||||||
switch len(args) {
|
|
||||||
case 0:
|
|
||||||
p.random.Seed(time.Now().UnixNano())
|
|
||||||
case 1:
|
|
||||||
p.randSeed = args[0].num()
|
|
||||||
p.random.Seed(int64(math.Float64bits(p.randSeed)))
|
|
||||||
}
|
|
||||||
return num(prevSeed), nil
|
|
||||||
|
|
||||||
case F_SYSTEM:
|
|
||||||
if p.noExec {
|
|
||||||
return null(), newError("can't call system() due to NoExec")
|
|
||||||
}
|
|
||||||
cmdline := p.toString(args[0])
|
|
||||||
cmd := p.execShell(cmdline)
|
|
||||||
cmd.Stdout = p.output
|
|
||||||
cmd.Stderr = p.errorOutput
|
|
||||||
_ = p.flushAll() // ensure synchronization
|
|
||||||
err := cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
p.printErrorf("%s\n", err)
|
|
||||||
return num(-1), nil
|
|
||||||
}
|
|
||||||
err = cmd.Wait()
|
|
||||||
if err != nil {
|
|
||||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
||||||
code := exitErr.ProcessState.ExitCode()
|
|
||||||
return num(float64(code)), nil
|
|
||||||
} else {
|
|
||||||
p.printErrorf("unexpected error running command %q: %v\n", cmdline, err)
|
|
||||||
return num(-1), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return num(0), nil
|
|
||||||
|
|
||||||
case F_CLOSE:
|
|
||||||
name := p.toString(args[0])
|
|
||||||
var c io.Closer = p.inputStreams[name]
|
|
||||||
if c != nil {
|
|
||||||
// Close input stream
|
|
||||||
delete(p.inputStreams, name)
|
|
||||||
err := c.Close()
|
|
||||||
if err != nil {
|
|
||||||
return num(-1), nil
|
|
||||||
}
|
|
||||||
return num(0), nil
|
|
||||||
}
|
|
||||||
c = p.outputStreams[name]
|
|
||||||
if c != nil {
|
|
||||||
// Close output stream
|
|
||||||
delete(p.outputStreams, name)
|
|
||||||
err := c.Close()
|
|
||||||
if err != nil {
|
|
||||||
return num(-1), nil
|
|
||||||
}
|
|
||||||
return num(0), nil
|
|
||||||
}
|
|
||||||
// Nothing to close
|
|
||||||
return num(-1), nil
|
|
||||||
|
|
||||||
case F_FFLUSH:
|
|
||||||
var name string
|
|
||||||
if len(args) > 0 {
|
|
||||||
name = p.toString(args[0])
|
|
||||||
}
|
|
||||||
var ok bool
|
|
||||||
if name != "" {
|
|
||||||
// Flush a single, named output stream
|
|
||||||
ok = p.flushStream(name)
|
|
||||||
} else {
|
|
||||||
// fflush() or fflush("") flushes all output streams
|
|
||||||
ok = p.flushAll()
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return num(-1), nil
|
|
||||||
}
|
|
||||||
return num(0), nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Shouldn't happen
|
|
||||||
panic(fmt.Sprintf("unexpected function: %s", op))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Executes code using configured system shell
|
|
||||||
func (p *interp) execShell(code string) *exec.Cmd {
|
|
||||||
executable := p.shellCommand[0]
|
|
||||||
args := p.shellCommand[1:]
|
|
||||||
args = append(args, code)
|
|
||||||
cmd := exec.Command(executable, args...)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call user-defined function with given index and arguments, return
|
|
||||||
// its return value (or null value if it doesn't return anything)
|
|
||||||
func (p *interp) callUser(index int, args []Expr) (value, error) {
|
|
||||||
f := p.program.Functions[index]
|
|
||||||
|
|
||||||
if p.callDepth >= maxCallDepth {
|
|
||||||
return null(), newError("calling %q exceeded maximum call depth of %d", f.Name, maxCallDepth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate the arguments and push them onto the locals stack
|
|
||||||
oldFrame := p.frame
|
|
||||||
newFrameStart := len(p.stack)
|
|
||||||
var arrays []int
|
|
||||||
for i, arg := range args {
|
|
||||||
if f.Arrays[i] {
|
|
||||||
a := arg.(*VarExpr)
|
|
||||||
arrays = append(arrays, p.getArrayIndex(a.Scope, a.Index))
|
|
||||||
} else {
|
|
||||||
argValue, err := p.eval(arg)
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
p.stack = append(p.stack, argValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Push zero value for any additional parameters (it's valid to
|
|
||||||
// call a function with fewer arguments than it has parameters)
|
|
||||||
oldArraysLen := len(p.arrays)
|
|
||||||
for i := len(args); i < len(f.Params); i++ {
|
|
||||||
if f.Arrays[i] {
|
|
||||||
arrays = append(arrays, len(p.arrays))
|
|
||||||
p.arrays = append(p.arrays, make(map[string]value))
|
|
||||||
} else {
|
|
||||||
p.stack = append(p.stack, null())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.frame = p.stack[newFrameStart:]
|
|
||||||
p.localArrays = append(p.localArrays, arrays)
|
|
||||||
|
|
||||||
// Execute the function!
|
|
||||||
p.callDepth++
|
|
||||||
err := p.executes(f.Body)
|
|
||||||
p.callDepth--
|
|
||||||
|
|
||||||
// Pop the locals off the stack
|
|
||||||
p.stack = p.stack[:newFrameStart]
|
|
||||||
p.frame = oldFrame
|
|
||||||
p.localArrays = p.localArrays[:len(p.localArrays)-1]
|
|
||||||
p.arrays = p.arrays[:oldArraysLen]
|
|
||||||
|
|
||||||
if r, ok := err.(returnValue); ok {
|
|
||||||
return r.Value, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
return null(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call native-defined function with given name and arguments, return
|
// Call native-defined function with given name and arguments, return
|
||||||
// its return value (or null value if it doesn't return anything).
|
// its return value (or null value if it doesn't return anything).
|
||||||
func (p *interp) callNative(index int, args []Expr) (value, error) {
|
func (p *interp) callNative(index int, args []value) (value, error) {
|
||||||
f := p.nativeFuncs[index]
|
f := p.nativeFuncs[index]
|
||||||
minIn := len(f.in) // Minimum number of args we should pass
|
minIn := len(f.in) // Minimum number of args we should pass
|
||||||
var variadicType reflect.Type
|
var variadicType reflect.Type
|
||||||
|
@ -401,11 +29,7 @@ func (p *interp) callNative(index int, args []Expr) (value, error) {
|
||||||
|
|
||||||
// Build list of args to pass to function
|
// Build list of args to pass to function
|
||||||
values := make([]reflect.Value, 0, 7) // up to 7 args won't require heap allocation
|
values := make([]reflect.Value, 0, 7) // up to 7 args won't require heap allocation
|
||||||
for i, arg := range args {
|
for i, a := range args {
|
||||||
a, err := p.eval(arg)
|
|
||||||
if err != nil {
|
|
||||||
return null(), err
|
|
||||||
}
|
|
||||||
var argType reflect.Type
|
var argType reflect.Type
|
||||||
if !f.isVariadic || i < len(f.in)-1 {
|
if !f.isVariadic || i < len(f.in)-1 {
|
||||||
argType = f.in[i]
|
argType = f.in[i]
|
||||||
|
@ -618,12 +242,12 @@ func validNativeType(typ reflect.Type) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guts of the split() function
|
// Guts of the split() function
|
||||||
func (p *interp) split(s string, scope VarScope, index int, fs string) (int, error) {
|
func (p *interp) split(s string, scope ast.VarScope, index int, fs string) (int, error) {
|
||||||
var parts []string
|
var parts []string
|
||||||
if fs == " " {
|
if fs == " " {
|
||||||
parts = strings.Fields(s)
|
parts = strings.Fields(s)
|
||||||
} else if s == "" {
|
} else if s == "" {
|
||||||
// NF should be 0 on empty line
|
// Leave parts 0 length on empty string
|
||||||
} else if utf8.RuneCountInString(fs) <= 1 {
|
} else if utf8.RuneCountInString(fs) <= 1 {
|
||||||
parts = strings.Split(s, fs)
|
parts = strings.Split(s, fs)
|
||||||
} else {
|
} else {
|
||||||
|
@ -637,7 +261,7 @@ func (p *interp) split(s string, scope VarScope, index int, fs string) (int, err
|
||||||
for i, part := range parts {
|
for i, part := range parts {
|
||||||
array[strconv.Itoa(i+1)] = numStr(part)
|
array[strconv.Itoa(i+1)] = numStr(part)
|
||||||
}
|
}
|
||||||
p.arrays[p.getArrayIndex(scope, index)] = array
|
p.arrays[p.arrayIndex(scope, index)] = array
|
||||||
return len(array), nil
|
return len(array), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -753,7 +377,7 @@ func (p *interp) sprintf(format string, args []value) (string, error) {
|
||||||
if len(types) > len(args) {
|
if len(types) > len(args) {
|
||||||
return "", newError("format error: got %d args, expected %d", len(args), len(types))
|
return "", newError("format error: got %d args, expected %d", len(args), len(types))
|
||||||
}
|
}
|
||||||
converted := make([]interface{}, len(types))
|
converted := make([]interface{}, 0, 7) // up to 7 args won't require heap allocation
|
||||||
for i, t := range types {
|
for i, t := range types {
|
||||||
a := args[i]
|
a := args[i]
|
||||||
var v interface{}
|
var v interface{}
|
||||||
|
@ -765,7 +389,7 @@ func (p *interp) sprintf(format string, args []value) (string, error) {
|
||||||
case 'f':
|
case 'f':
|
||||||
v = a.num()
|
v = a.num()
|
||||||
case 'u':
|
case 'u':
|
||||||
v = uint32(a.num())
|
v = uint(a.num())
|
||||||
case 'c':
|
case 'c':
|
||||||
var c []byte
|
var c []byte
|
||||||
n, isStr := a.isTrueStr()
|
n, isStr := a.isTrueStr()
|
||||||
|
@ -783,7 +407,7 @@ func (p *interp) sprintf(format string, args []value) (string, error) {
|
||||||
}
|
}
|
||||||
v = c
|
v = c
|
||||||
}
|
}
|
||||||
converted[i] = v
|
converted = append(converted, v)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(format, converted...), nil
|
return fmt.Sprintf(format, converted...), nil
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,16 +5,19 @@ package interp
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/csv"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
. "github.com/benhoyt/goawk/internal/ast"
|
"github.com/benhoyt/goawk/internal/ast"
|
||||||
. "github.com/benhoyt/goawk/lexer"
|
. "github.com/benhoyt/goawk/lexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,6 +30,73 @@ func (p *interp) printLine(writer io.Writer, line string) error {
|
||||||
return writeOutput(writer, p.outputRecordSep)
|
return writeOutput(writer, p.outputRecordSep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print given arguments followed by a newline (for "print" statement).
|
||||||
|
func (p *interp) printArgs(writer io.Writer, args []value) error {
|
||||||
|
switch p.outputMode {
|
||||||
|
case CSVMode, TSVMode:
|
||||||
|
fields := make([]string, 0, 7) // up to 7 args won't require a heap allocation
|
||||||
|
for _, arg := range args {
|
||||||
|
fields = append(fields, arg.str(p.outputFormat))
|
||||||
|
}
|
||||||
|
err := p.writeCSV(writer, fields)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Print OFS-separated args followed by ORS (usually newline).
|
||||||
|
for i, arg := range args {
|
||||||
|
if i > 0 {
|
||||||
|
err := writeOutput(writer, p.outputFieldSep)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := writeOutput(writer, arg.str(p.outputFormat))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := writeOutput(writer, p.outputRecordSep)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *interp) writeCSV(output io.Writer, fields []string) error {
|
||||||
|
// If output is already a *bufio.Writer (the common case), csv.NewWriter
|
||||||
|
// will use it directly. This is not explicitly documented, but
|
||||||
|
// csv.NewWriter calls bufio.NewWriter which calls bufio.NewWriterSize
|
||||||
|
// with a 4KB buffer, and bufio.NewWriterSize is documented as returning
|
||||||
|
// the underlying bufio.Writer if it's passed a large enough one.
|
||||||
|
var flush func() error
|
||||||
|
_, isBuffered := output.(*bufio.Writer)
|
||||||
|
if !isBuffered {
|
||||||
|
// Otherwise create a new buffered writer and flush after writing.
|
||||||
|
if p.csvOutput == nil {
|
||||||
|
p.csvOutput = bufio.NewWriterSize(output, 4096)
|
||||||
|
} else {
|
||||||
|
p.csvOutput.Reset(output)
|
||||||
|
}
|
||||||
|
output = p.csvOutput
|
||||||
|
flush = p.csvOutput.Flush
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the above, creating a new one of these is cheap.
|
||||||
|
writer := csv.NewWriter(output)
|
||||||
|
writer.Comma = p.csvOutputConfig.Separator
|
||||||
|
writer.UseCRLF = runtime.GOOS == "windows"
|
||||||
|
err := writer.Write(fields)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if flush != nil {
|
||||||
|
return flush()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Implement a buffered version of WriteCloser so output is buffered
|
// Implement a buffered version of WriteCloser so output is buffered
|
||||||
// when redirecting to a file (eg: print >"out")
|
// when redirecting to a file (eg: print >"out")
|
||||||
type bufferedWriteCloser struct {
|
type bufferedWriteCloser struct {
|
||||||
|
@ -49,16 +119,7 @@ func (wc *bufferedWriteCloser) Close() error {
|
||||||
|
|
||||||
// Determine the output stream for given redirect token and
|
// Determine the output stream for given redirect token and
|
||||||
// destination (file or pipe name)
|
// destination (file or pipe name)
|
||||||
func (p *interp) getOutputStream(redirect Token, dest Expr) (io.Writer, error) {
|
func (p *interp) getOutputStream(redirect Token, destValue value) (io.Writer, error) {
|
||||||
if redirect == ILLEGAL {
|
|
||||||
// Token "ILLEGAL" means send to standard output
|
|
||||||
return p.output, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
destValue, err := p.eval(dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
name := p.toString(destValue)
|
name := p.toString(destValue)
|
||||||
if _, ok := p.inputStreams[name]; ok {
|
if _, ok := p.inputStreams[name]; ok {
|
||||||
return nil, newError("can't write to reader stream")
|
return nil, newError("can't write to reader stream")
|
||||||
|
@ -121,6 +182,18 @@ func (p *interp) getOutputStream(redirect Token, dest Expr) (io.Writer, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Executes code using configured system shell
|
||||||
|
func (p *interp) execShell(code string) *exec.Cmd {
|
||||||
|
executable := p.shellCommand[0]
|
||||||
|
args := p.shellCommand[1:]
|
||||||
|
args = append(args, code)
|
||||||
|
if p.checkCtx {
|
||||||
|
return exec.CommandContext(p.ctx, executable, args...)
|
||||||
|
} else {
|
||||||
|
return exec.Command(executable, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get input Scanner to use for "getline" based on file name
|
// Get input Scanner to use for "getline" based on file name
|
||||||
func (p *interp) getInputScannerFile(name string) (*bufio.Scanner, error) {
|
func (p *interp) getInputScannerFile(name string) (*bufio.Scanner, error) {
|
||||||
if _, ok := p.outputStreams[name]; ok {
|
if _, ok := p.outputStreams[name]; ok {
|
||||||
|
@ -134,7 +207,7 @@ func (p *interp) getInputScannerFile(name string) (*bufio.Scanner, error) {
|
||||||
if scanner, ok := p.scanners["-"]; ok {
|
if scanner, ok := p.scanners["-"]; ok {
|
||||||
return scanner, nil
|
return scanner, nil
|
||||||
}
|
}
|
||||||
scanner := p.newScanner(p.stdin)
|
scanner := p.newScanner(p.stdin, make([]byte, inputBufSize))
|
||||||
p.scanners[name] = scanner
|
p.scanners[name] = scanner
|
||||||
return scanner, nil
|
return scanner, nil
|
||||||
}
|
}
|
||||||
|
@ -145,7 +218,7 @@ func (p *interp) getInputScannerFile(name string) (*bufio.Scanner, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err // *os.PathError is handled by caller (getline returns -1)
|
return nil, err // *os.PathError is handled by caller (getline returns -1)
|
||||||
}
|
}
|
||||||
scanner := p.newScanner(r)
|
scanner := p.newScanner(r, make([]byte, inputBufSize))
|
||||||
p.scanners[name] = scanner
|
p.scanners[name] = scanner
|
||||||
p.inputStreams[name] = r
|
p.inputStreams[name] = r
|
||||||
return scanner, nil
|
return scanner, nil
|
||||||
|
@ -175,7 +248,7 @@ func (p *interp) getInputScannerPipe(name string) (*bufio.Scanner, error) {
|
||||||
p.printErrorf("%s\n", err)
|
p.printErrorf("%s\n", err)
|
||||||
return bufio.NewScanner(strings.NewReader("")), nil
|
return bufio.NewScanner(strings.NewReader("")), nil
|
||||||
}
|
}
|
||||||
scanner := p.newScanner(r)
|
scanner := p.newScanner(r, make([]byte, inputBufSize))
|
||||||
p.commands[name] = cmd
|
p.commands[name] = cmd
|
||||||
p.inputStreams[name] = r
|
p.inputStreams[name] = r
|
||||||
p.scanners[name] = scanner
|
p.scanners[name] = scanner
|
||||||
|
@ -183,28 +256,53 @@ func (p *interp) getInputScannerPipe(name string) (*bufio.Scanner, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new buffered Scanner for reading input records
|
// Create a new buffered Scanner for reading input records
|
||||||
func (p *interp) newScanner(input io.Reader) *bufio.Scanner {
|
func (p *interp) newScanner(input io.Reader, buffer []byte) *bufio.Scanner {
|
||||||
scanner := bufio.NewScanner(input)
|
scanner := bufio.NewScanner(input)
|
||||||
switch {
|
switch {
|
||||||
|
case p.inputMode == CSVMode || p.inputMode == TSVMode:
|
||||||
|
splitter := csvSplitter{
|
||||||
|
separator: p.csvInputConfig.Separator,
|
||||||
|
sepLen: utf8.RuneLen(p.csvInputConfig.Separator),
|
||||||
|
comment: p.csvInputConfig.Comment,
|
||||||
|
header: p.csvInputConfig.Header,
|
||||||
|
fields: &p.fields,
|
||||||
|
setFieldNames: p.setFieldNames,
|
||||||
|
}
|
||||||
|
scanner.Split(splitter.scan)
|
||||||
case p.recordSep == "\n":
|
case p.recordSep == "\n":
|
||||||
// Scanner default is to split on newlines
|
// Scanner default is to split on newlines
|
||||||
case p.recordSep == "":
|
case p.recordSep == "":
|
||||||
// Empty string for RS means split on \n\n (blank lines)
|
// Empty string for RS means split on \n\n (blank lines)
|
||||||
splitter := blankLineSplitter{&p.recordTerminator}
|
splitter := blankLineSplitter{terminator: &p.recordTerminator}
|
||||||
scanner.Split(splitter.scan)
|
scanner.Split(splitter.scan)
|
||||||
case len(p.recordSep) == 1:
|
case len(p.recordSep) == 1:
|
||||||
splitter := byteSplitter{p.recordSep[0]}
|
splitter := byteSplitter{sep: p.recordSep[0]}
|
||||||
scanner.Split(splitter.scan)
|
scanner.Split(splitter.scan)
|
||||||
case utf8.RuneCountInString(p.recordSep) >= 1:
|
case utf8.RuneCountInString(p.recordSep) >= 1:
|
||||||
// Multi-byte and single char but multi-byte RS use regex
|
// Multi-byte and single char but multi-byte RS use regex
|
||||||
splitter := regexSplitter{p.recordSepRegex, &p.recordTerminator}
|
splitter := regexSplitter{re: p.recordSepRegex, terminator: &p.recordTerminator}
|
||||||
scanner.Split(splitter.scan)
|
scanner.Split(splitter.scan)
|
||||||
}
|
}
|
||||||
buffer := make([]byte, inputBufSize)
|
|
||||||
scanner.Buffer(buffer, maxRecordLength)
|
scanner.Buffer(buffer, maxRecordLength)
|
||||||
return scanner
|
return scanner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setFieldNames is called by csvSplitter.scan on the first row (if the
|
||||||
|
// "header" option is specified).
|
||||||
|
func (p *interp) setFieldNames(names []string) {
|
||||||
|
p.fieldNames = names
|
||||||
|
p.fieldIndexes = nil // clear name-to-index cache
|
||||||
|
|
||||||
|
// Populate FIELDS array (mapping of field indexes to field names).
|
||||||
|
fieldsArray := p.array(ast.ScopeGlobal, p.program.Arrays["FIELDS"])
|
||||||
|
for k := range fieldsArray {
|
||||||
|
delete(fieldsArray, k)
|
||||||
|
}
|
||||||
|
for i, name := range names {
|
||||||
|
fieldsArray[strconv.Itoa(i+1)] = str(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Copied from bufio/scan.go in the stdlib: I guess it's a bit more
|
// Copied from bufio/scan.go in the stdlib: I guess it's a bit more
|
||||||
// efficient than bytes.TrimSuffix(data, []byte("\r"))
|
// efficient than bytes.TrimSuffix(data, []byte("\r"))
|
||||||
func dropCR(data []byte) []byte {
|
func dropCR(data []byte) []byte {
|
||||||
|
@ -323,10 +421,222 @@ func (s regexSplitter) scan(data []byte, atEOF bool) (advance int, token []byte,
|
||||||
return 0, nil, nil
|
return 0, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Splitter that splits records in CSV or TSV format.
|
||||||
|
type csvSplitter struct {
|
||||||
|
separator rune
|
||||||
|
sepLen int
|
||||||
|
comment rune
|
||||||
|
header bool
|
||||||
|
|
||||||
|
recordBuffer []byte
|
||||||
|
fieldIndexes []int
|
||||||
|
noBOMCheck bool
|
||||||
|
|
||||||
|
fields *[]string
|
||||||
|
setFieldNames func(names []string)
|
||||||
|
rowNum int
|
||||||
|
}
|
||||||
|
|
||||||
|
// The structure of this code is taken from the stdlib encoding/csv Reader
|
||||||
|
// code, which is licensed under a compatible BSD-style license.
|
||||||
|
//
|
||||||
|
// We don't support all encoding/csv features: FieldsPerRecord is not
|
||||||
|
// supported, LazyQuotes is always on, and TrimLeadingSpace is always off.
|
||||||
|
func (s *csvSplitter) scan(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
|
// Some CSV files are saved with a UTF-8 BOM at the start; skip it.
|
||||||
|
if !s.noBOMCheck && len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
|
||||||
|
data = data[3:]
|
||||||
|
advance = 3
|
||||||
|
s.noBOMCheck = true
|
||||||
|
}
|
||||||
|
|
||||||
|
origData := data
|
||||||
|
if atEOF && len(data) == 0 {
|
||||||
|
// No more data, tell Scanner to stop.
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
readLine := func() []byte {
|
||||||
|
newline := bytes.IndexByte(data, '\n')
|
||||||
|
var line []byte
|
||||||
|
switch {
|
||||||
|
case newline >= 0:
|
||||||
|
// Process a single line (including newline).
|
||||||
|
line = data[:newline+1]
|
||||||
|
data = data[newline+1:]
|
||||||
|
case atEOF:
|
||||||
|
// If at EOF, we have a final record without a newline.
|
||||||
|
line = data
|
||||||
|
data = data[len(data):]
|
||||||
|
default:
|
||||||
|
// Need more data
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For backwards compatibility, drop trailing \r before EOF.
|
||||||
|
if len(line) > 0 && atEOF && line[len(line)-1] == '\r' {
|
||||||
|
line = line[:len(line)-1]
|
||||||
|
advance++
|
||||||
|
}
|
||||||
|
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read line (automatically skipping past empty lines and any comments).
|
||||||
|
skip := 0
|
||||||
|
var line []byte
|
||||||
|
for {
|
||||||
|
line = readLine()
|
||||||
|
if len(line) == 0 {
|
||||||
|
return 0, nil, nil // Request more data
|
||||||
|
}
|
||||||
|
if s.comment != 0 && nextRune(line) == s.comment {
|
||||||
|
advance += len(line)
|
||||||
|
skip += len(line)
|
||||||
|
continue // Skip comment lines
|
||||||
|
}
|
||||||
|
if len(line) == lenNewline(line) {
|
||||||
|
advance += len(line)
|
||||||
|
skip += len(line)
|
||||||
|
continue // Skip empty lines
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse each field in the record.
|
||||||
|
const quoteLen = len(`"`)
|
||||||
|
tokenHasCR := false
|
||||||
|
s.recordBuffer = s.recordBuffer[:0]
|
||||||
|
s.fieldIndexes = s.fieldIndexes[:0]
|
||||||
|
parseField:
|
||||||
|
for {
|
||||||
|
if len(line) == 0 || line[0] != '"' {
|
||||||
|
// Non-quoted string field
|
||||||
|
i := bytes.IndexRune(line, s.separator)
|
||||||
|
field := line
|
||||||
|
if i >= 0 {
|
||||||
|
advance += i + s.sepLen
|
||||||
|
field = field[:i]
|
||||||
|
} else {
|
||||||
|
advance += len(field)
|
||||||
|
field = field[:len(field)-lenNewline(field)]
|
||||||
|
}
|
||||||
|
s.recordBuffer = append(s.recordBuffer, field...)
|
||||||
|
s.fieldIndexes = append(s.fieldIndexes, len(s.recordBuffer))
|
||||||
|
if i >= 0 {
|
||||||
|
line = line[i+s.sepLen:]
|
||||||
|
continue parseField
|
||||||
|
}
|
||||||
|
break parseField
|
||||||
|
} else {
|
||||||
|
// Quoted string field
|
||||||
|
line = line[quoteLen:]
|
||||||
|
advance += quoteLen
|
||||||
|
for {
|
||||||
|
i := bytes.IndexByte(line, '"')
|
||||||
|
if i >= 0 {
|
||||||
|
// Hit next quote.
|
||||||
|
s.recordBuffer = append(s.recordBuffer, line[:i]...)
|
||||||
|
line = line[i+quoteLen:]
|
||||||
|
advance += i + quoteLen
|
||||||
|
switch rn := nextRune(line); {
|
||||||
|
case rn == '"':
|
||||||
|
// `""` sequence (append quote).
|
||||||
|
s.recordBuffer = append(s.recordBuffer, '"')
|
||||||
|
line = line[quoteLen:]
|
||||||
|
advance += quoteLen
|
||||||
|
case rn == s.separator:
|
||||||
|
// `",` sequence (end of field).
|
||||||
|
line = line[s.sepLen:]
|
||||||
|
s.fieldIndexes = append(s.fieldIndexes, len(s.recordBuffer))
|
||||||
|
advance += s.sepLen
|
||||||
|
continue parseField
|
||||||
|
case lenNewline(line) == len(line):
|
||||||
|
// `"\n` sequence (end of line).
|
||||||
|
s.fieldIndexes = append(s.fieldIndexes, len(s.recordBuffer))
|
||||||
|
advance += len(line)
|
||||||
|
break parseField
|
||||||
|
default:
|
||||||
|
// `"` sequence (bare quote).
|
||||||
|
s.recordBuffer = append(s.recordBuffer, '"')
|
||||||
|
}
|
||||||
|
} else if len(line) > 0 {
|
||||||
|
// Hit end of line (copy all data so far).
|
||||||
|
advance += len(line)
|
||||||
|
newlineLen := lenNewline(line)
|
||||||
|
if newlineLen == 2 {
|
||||||
|
tokenHasCR = true
|
||||||
|
s.recordBuffer = append(s.recordBuffer, line[:len(line)-2]...)
|
||||||
|
s.recordBuffer = append(s.recordBuffer, '\n')
|
||||||
|
} else {
|
||||||
|
s.recordBuffer = append(s.recordBuffer, line...)
|
||||||
|
}
|
||||||
|
line = readLine()
|
||||||
|
if line == nil {
|
||||||
|
return 0, nil, nil // Request more data
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Abrupt end of file.
|
||||||
|
s.fieldIndexes = append(s.fieldIndexes, len(s.recordBuffer))
|
||||||
|
advance += len(line)
|
||||||
|
break parseField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a single string and create slices out of it.
|
||||||
|
// This pins the memory of the fields together, but allocates once.
|
||||||
|
strBuf := string(s.recordBuffer) // Convert to string once to batch allocations
|
||||||
|
fields := make([]string, len(s.fieldIndexes))
|
||||||
|
preIdx := 0
|
||||||
|
for i, idx := range s.fieldIndexes {
|
||||||
|
fields[i] = strBuf[preIdx:idx]
|
||||||
|
preIdx = idx
|
||||||
|
}
|
||||||
|
|
||||||
|
s.noBOMCheck = true
|
||||||
|
|
||||||
|
if s.rowNum == 0 && s.header {
|
||||||
|
// Set header field names and advance, but don't return a line (token).
|
||||||
|
s.rowNum++
|
||||||
|
s.setFieldNames(fields)
|
||||||
|
return advance, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal row, set fields and return a line (token).
|
||||||
|
s.rowNum++
|
||||||
|
*s.fields = fields
|
||||||
|
token = origData[skip:advance]
|
||||||
|
token = token[:len(token)-lenNewline(token)]
|
||||||
|
if tokenHasCR {
|
||||||
|
token = bytes.ReplaceAll(token, []byte{'\r'}, nil)
|
||||||
|
}
|
||||||
|
return advance, token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lenNewline reports the number of bytes for the trailing \n.
|
||||||
|
func lenNewline(b []byte) int {
|
||||||
|
if len(b) > 0 && b[len(b)-1] == '\n' {
|
||||||
|
if len(b) > 1 && b[len(b)-2] == '\r' {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextRune returns the next rune in b or utf8.RuneError.
|
||||||
|
func nextRune(b []byte) rune {
|
||||||
|
r, _ := utf8.DecodeRune(b)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
// Setup for a new input file with given name (empty string if stdin)
|
// Setup for a new input file with given name (empty string if stdin)
|
||||||
func (p *interp) setFile(filename string) {
|
func (p *interp) setFile(filename string) {
|
||||||
p.filename = numStr(filename)
|
p.filename = numStr(filename)
|
||||||
p.fileLineNum = 0
|
p.fileLineNum = 0
|
||||||
|
p.hadFiles = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup for a new input line (but don't parse it into fields till we
|
// Setup for a new input line (but don't parse it into fields till we
|
||||||
|
@ -335,6 +645,7 @@ func (p *interp) setLine(line string, isTrueStr bool) {
|
||||||
p.line = line
|
p.line = line
|
||||||
p.lineIsTrueStr = isTrueStr
|
p.lineIsTrueStr = isTrueStr
|
||||||
p.haveFields = false
|
p.haveFields = false
|
||||||
|
p.reparseCSV = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the current line is parsed into fields, splitting it
|
// Ensure that the current line is parsed into fields, splitting it
|
||||||
|
@ -346,6 +657,23 @@ func (p *interp) ensureFields() {
|
||||||
p.haveFields = true
|
p.haveFields = true
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
case p.inputMode == CSVMode || p.inputMode == TSVMode:
|
||||||
|
if p.reparseCSV {
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(p.line))
|
||||||
|
scanner.Buffer(nil, maxRecordLength)
|
||||||
|
splitter := csvSplitter{
|
||||||
|
separator: p.csvInputConfig.Separator,
|
||||||
|
sepLen: utf8.RuneLen(p.csvInputConfig.Separator),
|
||||||
|
comment: p.csvInputConfig.Comment,
|
||||||
|
fields: &p.fields,
|
||||||
|
}
|
||||||
|
scanner.Split(splitter.scan)
|
||||||
|
if !scanner.Scan() {
|
||||||
|
p.fields = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Normally fields have already been parsed by csvSplitter
|
||||||
|
}
|
||||||
case p.fieldSep == " ":
|
case p.fieldSep == " ":
|
||||||
// FS space (default) means split fields on any whitespace
|
// FS space (default) means split fields on any whitespace
|
||||||
p.fields = strings.Fields(p.line)
|
p.fields = strings.Fields(p.line)
|
||||||
|
@ -362,7 +690,7 @@ func (p *interp) ensureFields() {
|
||||||
// Special case for when RS=="" and FS is single character,
|
// Special case for when RS=="" and FS is single character,
|
||||||
// split on newline in addition to FS. See more here:
|
// split on newline in addition to FS. See more here:
|
||||||
// https://www.gnu.org/software/gawk/manual/html_node/Multiple-Line.html
|
// https://www.gnu.org/software/gawk/manual/html_node/Multiple-Line.html
|
||||||
if p.recordSep == "" && utf8.RuneCountInString(p.fieldSep) == 1 {
|
if p.inputMode == DefaultMode && p.recordSep == "" && utf8.RuneCountInString(p.fieldSep) == 1 {
|
||||||
fields := make([]string, 0, len(p.fields))
|
fields := make([]string, 0, len(p.fields))
|
||||||
for _, field := range p.fields {
|
for _, field := range p.fields {
|
||||||
lines := strings.Split(field, "\n")
|
lines := strings.Split(field, "\n")
|
||||||
|
@ -374,7 +702,10 @@ func (p *interp) ensureFields() {
|
||||||
p.fields = fields
|
p.fields = fields
|
||||||
}
|
}
|
||||||
|
|
||||||
p.fieldsIsTrueStr = make([]bool, len(p.fields))
|
p.fieldsIsTrueStr = p.fieldsIsTrueStr[:0] // avoid allocation most of the time
|
||||||
|
for range p.fields {
|
||||||
|
p.fieldsIsTrueStr = append(p.fieldsIsTrueStr, false)
|
||||||
|
}
|
||||||
p.numFields = len(p.fields)
|
p.numFields = len(p.fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,8 +722,7 @@ func (p *interp) nextLine() (string, error) {
|
||||||
// Moved past number of ARGV args and haven't seen
|
// Moved past number of ARGV args and haven't seen
|
||||||
// any files yet, use stdin
|
// any files yet, use stdin
|
||||||
p.input = p.stdin
|
p.input = p.stdin
|
||||||
p.setFile("")
|
p.setFile("-")
|
||||||
p.hadFiles = true
|
|
||||||
} else {
|
} else {
|
||||||
if p.filenameIndex >= p.argc {
|
if p.filenameIndex >= p.argc {
|
||||||
// Done with ARGV args, all done with input
|
// Done with ARGV args, all done with input
|
||||||
|
@ -403,15 +733,24 @@ func (p *interp) nextLine() (string, error) {
|
||||||
// not present
|
// not present
|
||||||
index := strconv.Itoa(p.filenameIndex)
|
index := strconv.Itoa(p.filenameIndex)
|
||||||
argvIndex := p.program.Arrays["ARGV"]
|
argvIndex := p.program.Arrays["ARGV"]
|
||||||
argvArray := p.arrays[p.getArrayIndex(ScopeGlobal, argvIndex)]
|
argvArray := p.array(ast.ScopeGlobal, argvIndex)
|
||||||
filename := p.toString(argvArray[index])
|
filename := p.toString(argvArray[index])
|
||||||
p.filenameIndex++
|
p.filenameIndex++
|
||||||
|
|
||||||
// Is it actually a var=value assignment?
|
// Is it actually a var=value assignment?
|
||||||
matches := varRegex.FindStringSubmatch(filename)
|
var matches []string
|
||||||
|
if !p.noArgVars {
|
||||||
|
matches = varRegex.FindStringSubmatch(filename)
|
||||||
|
}
|
||||||
if len(matches) >= 3 {
|
if len(matches) >= 3 {
|
||||||
// Yep, set variable to value and keep going
|
// Yep, set variable to value and keep going
|
||||||
err := p.setVarByName(matches[1], matches[2])
|
name, val := matches[1], matches[2]
|
||||||
|
// Oddly, var=value args must interpret escapes (issue #129)
|
||||||
|
unescaped, err := Unescape(val)
|
||||||
|
if err == nil {
|
||||||
|
val = unescaped
|
||||||
|
}
|
||||||
|
err = p.setVarByName(name, val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -423,7 +762,7 @@ func (p *interp) nextLine() (string, error) {
|
||||||
} else if filename == "-" {
|
} else if filename == "-" {
|
||||||
// ARGV arg is "-" meaning stdin
|
// ARGV arg is "-" meaning stdin
|
||||||
p.input = p.stdin
|
p.input = p.stdin
|
||||||
p.setFile("")
|
p.setFile("-")
|
||||||
} else {
|
} else {
|
||||||
// A regular file name, open it
|
// A regular file name, open it
|
||||||
if p.noFileReads {
|
if p.noFileReads {
|
||||||
|
@ -435,10 +774,12 @@ func (p *interp) nextLine() (string, error) {
|
||||||
}
|
}
|
||||||
p.input = input
|
p.input = input
|
||||||
p.setFile(filename)
|
p.setFile(filename)
|
||||||
p.hadFiles = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.scanner = p.newScanner(p.input)
|
if p.inputBuffer == nil { // reuse buffer from last input file
|
||||||
|
p.inputBuffer = make([]byte, inputBufSize)
|
||||||
|
}
|
||||||
|
p.scanner = p.newScanner(p.input, p.inputBuffer)
|
||||||
}
|
}
|
||||||
p.recordTerminator = p.recordSep // will be overridden if RS is "" or multiple chars
|
p.recordTerminator = p.recordSep // will be overridden if RS is "" or multiple chars
|
||||||
if p.scanner.Scan() {
|
if p.scanner.Scan() {
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
// The New...Execute API (allows you to efficiently execute the same program repeatedly).
|
||||||
|
|
||||||
|
package interp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/benhoyt/goawk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const checkContextOps = 1000 // for efficiency, only check context every N instructions
|
||||||
|
|
||||||
|
// Interpreter is an interpreter for a specific program, allowing you to
|
||||||
|
// efficiently execute the same program over and over with different inputs.
|
||||||
|
// Use New to create an Interpreter.
|
||||||
|
//
|
||||||
|
// Most programs won't need reusable execution, and should use the simpler
|
||||||
|
// Exec or ExecProgram functions instead.
|
||||||
|
type Interpreter struct {
|
||||||
|
interp *interp
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a reusable interpreter for the given program.
|
||||||
|
//
|
||||||
|
// Most programs won't need reusable execution, and should use the simpler
|
||||||
|
// Exec or ExecProgram functions instead.
|
||||||
|
func New(program *parser.Program) (*Interpreter, error) {
|
||||||
|
p := newInterp(program)
|
||||||
|
return &Interpreter{interp: p}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute runs this program with the given execution configuration (input,
|
||||||
|
// output, and variables) and returns the exit status code of the program. A
|
||||||
|
// nil config is valid and will use the defaults (zero values).
|
||||||
|
//
|
||||||
|
// Internal memory allocations are reused, so calling Execute on the same
|
||||||
|
// Interpreter instance is significantly more efficient than calling
|
||||||
|
// ExecProgram multiple times.
|
||||||
|
//
|
||||||
|
// I/O state is reset between each run, but variables and the random number
|
||||||
|
// generator seed are not; use ResetVars and ResetRand to reset those.
|
||||||
|
//
|
||||||
|
// It's best to set config.Environ to a non-nil slice, otherwise Execute will
|
||||||
|
// call the relatively inefficient os.Environ each time. Set config.Environ to
|
||||||
|
// []string{} if the script doesn't need environment variables, or call
|
||||||
|
// os.Environ once and set config.Environ to that value each execution.
|
||||||
|
//
|
||||||
|
// Note that config.Funcs must be the same value provided to
|
||||||
|
// parser.ParseProgram, and must not change between calls to Execute.
|
||||||
|
func (p *Interpreter) Execute(config *Config) (int, error) {
|
||||||
|
p.interp.resetCore()
|
||||||
|
p.interp.checkCtx = false
|
||||||
|
|
||||||
|
err := p.interp.setExecuteConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.interp.executeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *interp) resetCore() {
|
||||||
|
p.scanner = nil
|
||||||
|
for k := range p.scanners {
|
||||||
|
delete(p.scanners, k)
|
||||||
|
}
|
||||||
|
p.input = nil
|
||||||
|
for k := range p.inputStreams {
|
||||||
|
delete(p.inputStreams, k)
|
||||||
|
}
|
||||||
|
for k := range p.outputStreams {
|
||||||
|
delete(p.outputStreams, k)
|
||||||
|
}
|
||||||
|
for k := range p.commands {
|
||||||
|
delete(p.commands, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.sp = 0
|
||||||
|
p.localArrays = p.localArrays[:0]
|
||||||
|
p.callDepth = 0
|
||||||
|
|
||||||
|
p.filename = null()
|
||||||
|
p.line = ""
|
||||||
|
p.lineIsTrueStr = false
|
||||||
|
p.lineNum = 0
|
||||||
|
p.fileLineNum = 0
|
||||||
|
p.fields = nil
|
||||||
|
p.fieldsIsTrueStr = nil
|
||||||
|
p.numFields = 0
|
||||||
|
p.haveFields = false
|
||||||
|
|
||||||
|
p.exitStatus = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *interp) resetVars() {
|
||||||
|
// Reset global scalars
|
||||||
|
for i := range p.globals {
|
||||||
|
p.globals[i] = null()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset global arrays
|
||||||
|
for _, array := range p.arrays {
|
||||||
|
for k := range array {
|
||||||
|
delete(array, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset special variables
|
||||||
|
p.convertFormat = "%.6g"
|
||||||
|
p.outputFormat = "%.6g"
|
||||||
|
p.fieldSep = " "
|
||||||
|
p.fieldSepRegex = nil
|
||||||
|
p.recordSep = "\n"
|
||||||
|
p.recordSepRegex = nil
|
||||||
|
p.recordTerminator = ""
|
||||||
|
p.outputFieldSep = " "
|
||||||
|
p.outputRecordSep = "\n"
|
||||||
|
p.subscriptSep = "\x1c"
|
||||||
|
p.matchLength = 0
|
||||||
|
p.matchStart = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetVars resets this interpreter's variables, setting scalar variables to
|
||||||
|
// null, clearing arrays, and resetting special variables such as FS and RS to
|
||||||
|
// their defaults.
|
||||||
|
func (p *Interpreter) ResetVars() {
|
||||||
|
p.interp.resetVars()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetRand resets this interpreter's random number generator seed, so that
|
||||||
|
// rand() produces the same sequence it would have after calling New. This is
|
||||||
|
// a relatively CPU-intensive operation.
|
||||||
|
func (p *Interpreter) ResetRand() {
|
||||||
|
p.interp.randSeed = 1.0
|
||||||
|
p.interp.random.Seed(int64(math.Float64bits(p.interp.randSeed)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteContext is like Execute, but takes a context to allow the caller to
|
||||||
|
// set an execution timeout or cancel the execution. For efficiency, the
|
||||||
|
// context is only tested every 1000 virtual machine instructions.
|
||||||
|
//
|
||||||
|
// Context handling is not preemptive: currently long-running operations like
|
||||||
|
// system() won't be interrupted.
|
||||||
|
func (p *Interpreter) ExecuteContext(ctx context.Context, config *Config) (int, error) {
|
||||||
|
p.interp.resetCore()
|
||||||
|
p.interp.checkCtx = ctx != context.Background() && ctx != context.TODO()
|
||||||
|
p.interp.ctx = ctx
|
||||||
|
p.interp.ctxDone = ctx.Done()
|
||||||
|
p.interp.ctxOps = 0
|
||||||
|
|
||||||
|
err := p.interp.setExecuteConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.interp.executeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *interp) checkContext() error {
|
||||||
|
p.ctxOps++
|
||||||
|
if p.ctxOps < checkContextOps {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.ctxOps = 0
|
||||||
|
return p.checkContextNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *interp) checkContextNow() error {
|
||||||
|
select {
|
||||||
|
case <-p.ctxDone:
|
||||||
|
return p.ctx.Err()
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,6 +53,20 @@ func boolean(b bool) value {
|
||||||
return num(0)
|
return num(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of v for debugging.
|
||||||
|
func (v value) String() string {
|
||||||
|
switch v.typ {
|
||||||
|
case typeStr:
|
||||||
|
return fmt.Sprintf("str(%q)", v.s)
|
||||||
|
case typeNum:
|
||||||
|
return fmt.Sprintf("num(%s)", v.str("%.6g"))
|
||||||
|
case typeNumStr:
|
||||||
|
return fmt.Sprintf("numStr(%q)", v.s)
|
||||||
|
default:
|
||||||
|
return "null()"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return true if value is a "true string" (a string or a "numeric string"
|
// Return true if value is a "true string" (a string or a "numeric string"
|
||||||
// from an input field that can't be converted to a number). If false,
|
// from an input field that can't be converted to a number). If false,
|
||||||
// also return the (possibly converted) number.
|
// also return the (possibly converted) number.
|
||||||
|
@ -61,7 +75,7 @@ func (v value) isTrueStr() (float64, bool) {
|
||||||
case typeStr:
|
case typeStr:
|
||||||
return 0, true
|
return 0, true
|
||||||
case typeNumStr:
|
case typeNumStr:
|
||||||
f, err := strconv.ParseFloat(strings.TrimSpace(v.s), 64)
|
f, err := parseFloat(v.s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, true
|
return 0, true
|
||||||
}
|
}
|
||||||
|
@ -79,7 +93,7 @@ func (v value) boolean() bool {
|
||||||
case typeStr:
|
case typeStr:
|
||||||
return v.s != ""
|
return v.s != ""
|
||||||
case typeNumStr:
|
case typeNumStr:
|
||||||
f, err := strconv.ParseFloat(strings.TrimSpace(v.s), 64)
|
f, err := parseFloat(v.s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return v.s != ""
|
return v.s != ""
|
||||||
}
|
}
|
||||||
|
@ -89,6 +103,30 @@ func (v value) boolean() bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Like strconv.ParseFloat, but allow hex floating point without exponent, and
|
||||||
|
// allow "+nan" and "-nan" (though they both return math.NaN()). Also disallow
|
||||||
|
// underscore digit separators.
|
||||||
|
func parseFloat(s string) (float64, error) {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if len(s) > 1 && (s[0] == '+' || s[0] == '-') {
|
||||||
|
if len(s) == 4 && hasNaNPrefix(s[1:]) {
|
||||||
|
// ParseFloat doesn't handle "nan" with sign prefix, so handle it here.
|
||||||
|
return math.NaN(), nil
|
||||||
|
}
|
||||||
|
if len(s) > 3 && hasHexPrefix(s[1:]) && strings.IndexByte(s, 'p') < 0 {
|
||||||
|
s += "p0"
|
||||||
|
}
|
||||||
|
} else if len(s) > 2 && hasHexPrefix(s) && strings.IndexByte(s, 'p') < 0 {
|
||||||
|
s += "p0"
|
||||||
|
}
|
||||||
|
n, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err == nil && strings.IndexByte(s, '_') >= 0 {
|
||||||
|
// Underscore separators aren't supported by AWK.
|
||||||
|
return 0, strconv.ErrSyntax
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
// Return value's string value, or convert to a string using given
|
// Return value's string value, or convert to a string using given
|
||||||
// format if a number value. Integers are a special case and don't
|
// format if a number value. Integers are a special case and don't
|
||||||
// use floatFormat.
|
// use floatFormat.
|
||||||
|
@ -106,6 +144,9 @@ func (v value) str(floatFormat string) string {
|
||||||
case v.n == float64(int(v.n)):
|
case v.n == float64(int(v.n)):
|
||||||
return strconv.Itoa(int(v.n))
|
return strconv.Itoa(int(v.n))
|
||||||
default:
|
default:
|
||||||
|
if floatFormat == "%.6g" {
|
||||||
|
return strconv.FormatFloat(v.n, 'g', 6, 64)
|
||||||
|
}
|
||||||
return fmt.Sprintf(floatFormat, v.n)
|
return fmt.Sprintf(floatFormat, v.n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,20 +178,35 @@ func parseFloatPrefix(s string) float64 {
|
||||||
}
|
}
|
||||||
start := i
|
start := i
|
||||||
|
|
||||||
// Parse mantissa: optional sign, initial digit(s), optional '.',
|
// Parse optional sign and check for NaN and Inf.
|
||||||
// then more digits
|
|
||||||
gotDigit := false
|
|
||||||
if i < len(s) && (s[i] == '+' || s[i] == '-') {
|
if i < len(s) && (s[i] == '+' || s[i] == '-') {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
|
if i+3 <= len(s) {
|
||||||
|
if hasNaNPrefix(s[i:]) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
if hasInfPrefix(s[i:]) {
|
||||||
|
if s[start] == '-' {
|
||||||
|
return math.Inf(-1)
|
||||||
|
}
|
||||||
|
return math.Inf(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse mantissa: initial digit(s), optional '.', then more digits
|
||||||
|
if i+2 < len(s) && hasHexPrefix(s[i:]) {
|
||||||
|
return parseHexFloatPrefix(s, start, i+2)
|
||||||
|
}
|
||||||
|
gotDigit := false
|
||||||
|
for i < len(s) && isDigit(s[i]) {
|
||||||
gotDigit = true
|
gotDigit = true
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
if i < len(s) && s[i] == '.' {
|
if i < len(s) && s[i] == '.' {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
|
for i < len(s) && isDigit(s[i]) {
|
||||||
gotDigit = true
|
gotDigit = true
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
@ -166,7 +222,7 @@ func parseFloatPrefix(s string) float64 {
|
||||||
if i < len(s) && (s[i] == '+' || s[i] == '-') {
|
if i < len(s) && (s[i] == '+' || s[i] == '-') {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
|
for i < len(s) && isDigit(s[i]) {
|
||||||
i++
|
i++
|
||||||
end = i
|
end = i
|
||||||
}
|
}
|
||||||
|
@ -176,3 +232,63 @@ func parseFloatPrefix(s string) float64 {
|
||||||
f, _ := strconv.ParseFloat(floatStr, 64)
|
f, _ := strconv.ParseFloat(floatStr, 64)
|
||||||
return f // Returns infinity in case of "value out of range" error
|
return f // Returns infinity in case of "value out of range" error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasHexPrefix(s string) bool {
|
||||||
|
return s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasNaNPrefix(s string) bool {
|
||||||
|
return (s[0] == 'n' || s[0] == 'N') && (s[1] == 'a' || s[1] == 'A') && (s[2] == 'n' || s[2] == 'N')
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasInfPrefix(s string) bool {
|
||||||
|
return (s[0] == 'i' || s[0] == 'I') && (s[1] == 'n' || s[1] == 'N') && (s[2] == 'f' || s[2] == 'F')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper used by parseFloatPrefix to handle hexadecimal floating point.
|
||||||
|
func parseHexFloatPrefix(s string, start, i int) float64 {
|
||||||
|
gotDigit := false
|
||||||
|
for i < len(s) && isHexDigit(s[i]) {
|
||||||
|
gotDigit = true
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i < len(s) && s[i] == '.' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for i < len(s) && isHexDigit(s[i]) {
|
||||||
|
gotDigit = true
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if !gotDigit {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
gotExponent := false
|
||||||
|
end := i
|
||||||
|
if i < len(s) && (s[i] == 'p' || s[i] == 'P') {
|
||||||
|
i++
|
||||||
|
if i < len(s) && (s[i] == '+' || s[i] == '-') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for i < len(s) && isDigit(s[i]) {
|
||||||
|
gotExponent = true
|
||||||
|
i++
|
||||||
|
end = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
floatStr := s[start:end]
|
||||||
|
if !gotExponent {
|
||||||
|
floatStr += "p0" // AWK allows "0x12", ParseFloat requires "0x12p0"
|
||||||
|
}
|
||||||
|
f, _ := strconv.ParseFloat(floatStr, 64)
|
||||||
|
return f // Returns infinity in case of "value out of range" error
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDigit(c byte) bool {
|
||||||
|
return c >= '0' && c <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHexDigit(c byte) bool {
|
||||||
|
return c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,11 +5,10 @@
|
||||||
//
|
//
|
||||||
// To tokenize some source, create a new lexer with NewLexer(src) and
|
// To tokenize some source, create a new lexer with NewLexer(src) and
|
||||||
// then call Scan() until the token type is EOF or ILLEGAL.
|
// then call Scan() until the token type is EOF or ILLEGAL.
|
||||||
//
|
|
||||||
package lexer
|
package lexer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lexer tokenizes a byte string of AWK source code. Use NewLexer to
|
// Lexer tokenizes a byte string of AWK source code. Use NewLexer to
|
||||||
|
@ -120,6 +119,8 @@ func (l *Lexer) scan() (Position, Token, string) {
|
||||||
switch ch {
|
switch ch {
|
||||||
case '$':
|
case '$':
|
||||||
tok = DOLLAR
|
tok = DOLLAR
|
||||||
|
case '@':
|
||||||
|
tok = AT
|
||||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
|
||||||
// Avoid make/append and use l.offset directly for performance
|
// Avoid make/append and use l.offset directly for performance
|
||||||
start := l.offset - 2
|
start := l.offset - 2
|
||||||
|
@ -184,80 +185,18 @@ func (l *Lexer) scan() (Position, Token, string) {
|
||||||
}
|
}
|
||||||
case '"', '\'':
|
case '"', '\'':
|
||||||
// Note: POSIX awk spec doesn't allow single-quoted strings,
|
// Note: POSIX awk spec doesn't allow single-quoted strings,
|
||||||
// but this helps without quoting, especially on Windows
|
// but this helps with quoting, especially on Windows
|
||||||
// where the shell quote character is " (double quote).
|
// where the shell quote character is " (double quote).
|
||||||
chars := make([]byte, 0, 32) // most won't require heap allocation
|
s, err := parseString(ch, func() byte { return l.ch }, l.next)
|
||||||
for l.ch != ch {
|
if err != nil {
|
||||||
c := l.ch
|
return l.pos, ILLEGAL, err.Error()
|
||||||
if c == 0 {
|
}
|
||||||
return l.pos, ILLEGAL, "didn't find end quote in string"
|
if l.ch != ch {
|
||||||
}
|
return l.pos, ILLEGAL, "didn't find end quote in string"
|
||||||
if c == '\r' || c == '\n' {
|
|
||||||
return l.pos, ILLEGAL, "can't have newline in string"
|
|
||||||
}
|
|
||||||
if c != '\\' {
|
|
||||||
// Normal, non-escaped character
|
|
||||||
chars = append(chars, c)
|
|
||||||
l.next()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Escape sequence, skip over \ and process
|
|
||||||
l.next()
|
|
||||||
switch l.ch {
|
|
||||||
case 'n':
|
|
||||||
c = '\n'
|
|
||||||
l.next()
|
|
||||||
case 't':
|
|
||||||
c = '\t'
|
|
||||||
l.next()
|
|
||||||
case 'r':
|
|
||||||
c = '\r'
|
|
||||||
l.next()
|
|
||||||
case 'a':
|
|
||||||
c = '\a'
|
|
||||||
l.next()
|
|
||||||
case 'b':
|
|
||||||
c = '\b'
|
|
||||||
l.next()
|
|
||||||
case 'f':
|
|
||||||
c = '\f'
|
|
||||||
l.next()
|
|
||||||
case 'v':
|
|
||||||
c = '\v'
|
|
||||||
l.next()
|
|
||||||
case 'x':
|
|
||||||
// Hex byte of one of two hex digits
|
|
||||||
l.next()
|
|
||||||
digit := hexDigit(l.ch)
|
|
||||||
if digit < 0 {
|
|
||||||
return l.pos, ILLEGAL, "1 or 2 hex digits expected"
|
|
||||||
}
|
|
||||||
c = byte(digit)
|
|
||||||
l.next()
|
|
||||||
digit = hexDigit(l.ch)
|
|
||||||
if digit >= 0 {
|
|
||||||
c = c*16 + byte(digit)
|
|
||||||
l.next()
|
|
||||||
}
|
|
||||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
|
||||||
// Octal byte of 1-3 octal digits
|
|
||||||
c = l.ch - '0'
|
|
||||||
l.next()
|
|
||||||
for i := 0; i < 2 && l.ch >= '0' && l.ch <= '7'; i++ {
|
|
||||||
c = c*8 + l.ch - '0'
|
|
||||||
l.next()
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Any other escape character is just the char
|
|
||||||
// itself, eg: "\z" is just "z"
|
|
||||||
c = l.ch
|
|
||||||
l.next()
|
|
||||||
}
|
|
||||||
chars = append(chars, c)
|
|
||||||
}
|
}
|
||||||
l.next()
|
l.next()
|
||||||
tok = STRING
|
tok = STRING
|
||||||
val = string(chars)
|
val = s
|
||||||
case '(':
|
case '(':
|
||||||
tok = LPAREN
|
tok = LPAREN
|
||||||
case ')':
|
case ')':
|
||||||
|
@ -366,7 +305,7 @@ func (l *Lexer) scanRegex() (Position, Token, string) {
|
||||||
pos.Column -= 2
|
pos.Column -= 2
|
||||||
chars = append(chars, '=')
|
chars = append(chars, '=')
|
||||||
default:
|
default:
|
||||||
return l.pos, ILLEGAL, fmt.Sprintf("unexpected %s preceding regex", l.lastTok)
|
panic("ScanRegex should only be called after DIV or DIV_ASSIGN token")
|
||||||
}
|
}
|
||||||
for l.ch != '/' {
|
for l.ch != '/' {
|
||||||
c := l.ch
|
c := l.ch
|
||||||
|
@ -424,7 +363,7 @@ func (l *Lexer) unread() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isNameStart(ch byte) bool {
|
func isNameStart(ch byte) bool {
|
||||||
return ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
|
return ch == '_' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z'
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDigit(ch byte) bool {
|
func isDigit(ch byte) bool {
|
||||||
|
@ -459,3 +398,102 @@ func (l *Lexer) choice(ch byte, one, two Token) Token {
|
||||||
func (l *Lexer) PeekByte() byte {
|
func (l *Lexer) PeekByte() byte {
|
||||||
return l.ch
|
return l.ch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unescape unescapes the backslash escapes in s (which shouldn't include the
|
||||||
|
// surrounding quotes) and returns the unquoted string. It's intended for use
|
||||||
|
// when unescaping command line var=value assignments, as required by the
|
||||||
|
// POSIX AWK spec.
|
||||||
|
func Unescape(s string) (string, error) {
|
||||||
|
i := 0
|
||||||
|
ch := func() byte {
|
||||||
|
if i >= len(s) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return s[i]
|
||||||
|
}
|
||||||
|
next := func() {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return parseString(0, ch, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a string ending with given quote character (not parsed). The ch
|
||||||
|
// function returns the current character (or 0 at the end); the next function
|
||||||
|
// moves forward one character.
|
||||||
|
func parseString(quote byte, ch func() byte, next func()) (string, error) {
|
||||||
|
chars := make([]byte, 0, 32) // most strings won't require heap allocation
|
||||||
|
for {
|
||||||
|
c := ch()
|
||||||
|
if c == quote || c == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if c == '\r' || c == '\n' {
|
||||||
|
return "", errors.New("can't have newline in string")
|
||||||
|
}
|
||||||
|
if c != '\\' {
|
||||||
|
// Normal, non-escaped character
|
||||||
|
chars = append(chars, c)
|
||||||
|
next()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Escape sequence, skip over \ and process
|
||||||
|
next()
|
||||||
|
switch ch() {
|
||||||
|
case 'n':
|
||||||
|
c = '\n'
|
||||||
|
next()
|
||||||
|
case 't':
|
||||||
|
c = '\t'
|
||||||
|
next()
|
||||||
|
case 'r':
|
||||||
|
c = '\r'
|
||||||
|
next()
|
||||||
|
case 'a':
|
||||||
|
c = '\a'
|
||||||
|
next()
|
||||||
|
case 'b':
|
||||||
|
c = '\b'
|
||||||
|
next()
|
||||||
|
case 'f':
|
||||||
|
c = '\f'
|
||||||
|
next()
|
||||||
|
case 'v':
|
||||||
|
c = '\v'
|
||||||
|
next()
|
||||||
|
case 'x':
|
||||||
|
// Hex byte of one of two hex digits
|
||||||
|
next()
|
||||||
|
digit := hexDigit(ch())
|
||||||
|
if digit < 0 {
|
||||||
|
return "", errors.New("1 or 2 hex digits expected")
|
||||||
|
}
|
||||||
|
c = byte(digit)
|
||||||
|
next()
|
||||||
|
digit = hexDigit(ch())
|
||||||
|
if digit >= 0 {
|
||||||
|
c = c*16 + byte(digit)
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||||
|
// Octal byte of 1-3 octal digits
|
||||||
|
c = ch() - '0'
|
||||||
|
next()
|
||||||
|
for i := 0; i < 2 && ch() >= '0' && ch() <= '7'; i++ {
|
||||||
|
c = c*8 + ch() - '0'
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Any other escape character is just the char
|
||||||
|
// itself, eg: "\z" is just "z".
|
||||||
|
c = ch()
|
||||||
|
if c == 0 {
|
||||||
|
// Expect backslash right at the end of the string, which is
|
||||||
|
// interpreted as a literal backslash (only for Unescape).
|
||||||
|
c = '\\'
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
chars = append(chars, c)
|
||||||
|
}
|
||||||
|
return string(chars), nil
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ const (
|
||||||
AND
|
AND
|
||||||
APPEND
|
APPEND
|
||||||
ASSIGN
|
ASSIGN
|
||||||
|
AT
|
||||||
COLON
|
COLON
|
||||||
COMMA
|
COMMA
|
||||||
DECR
|
DECR
|
||||||
|
@ -172,6 +173,7 @@ var tokenNames = map[Token]string{
|
||||||
AND: "&&",
|
AND: "&&",
|
||||||
APPEND: ">>",
|
APPEND: ">>",
|
||||||
ASSIGN: "=",
|
ASSIGN: "=",
|
||||||
|
AT: "@",
|
||||||
COLON: ":",
|
COLON: ":",
|
||||||
COMMA: ",",
|
COMMA: ",",
|
||||||
DECR: "--",
|
DECR: "--",
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
// Package parser is an AWK parser and abstract syntax tree.
|
// Package parser is an AWK parser and abstract syntax tree.
|
||||||
//
|
//
|
||||||
// Use the ParseProgram function to parse an AWK program, and then
|
// Use the ParseProgram function to parse an AWK program, and then give the
|
||||||
// give the result to one of the interp.Exec* functions to execute it.
|
// result to interp.Exec, interp.ExecProgram, or interp.New to execute it.
|
||||||
//
|
|
||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -12,7 +11,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
. "github.com/benhoyt/goawk/internal/ast"
|
"github.com/benhoyt/goawk/internal/ast"
|
||||||
|
"github.com/benhoyt/goawk/internal/compiler"
|
||||||
. "github.com/benhoyt/goawk/lexer"
|
. "github.com/benhoyt/goawk/lexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,40 +68,52 @@ func ParseProgram(src []byte, config *ParserConfig) (prog *Program, err error) {
|
||||||
}
|
}
|
||||||
p.initResolve()
|
p.initResolve()
|
||||||
p.next() // initialize p.tok
|
p.next() // initialize p.tok
|
||||||
return p.program(), nil
|
|
||||||
|
// Parse into abstract syntax tree
|
||||||
|
prog = p.program()
|
||||||
|
|
||||||
|
// Compile to virtual machine code
|
||||||
|
prog.Compiled, err = compiler.Compile(prog.toAST())
|
||||||
|
return prog, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Program is the abstract syntax tree for an entire AWK program.
|
// Program is the parsed and compiled representation of an entire AWK program.
|
||||||
type Program struct {
|
type Program struct {
|
||||||
// These fields aren't intended to be used or modified directly,
|
// These fields aren't intended to be used or modified directly,
|
||||||
// but are exported for the interpreter (Program itself needs to
|
// but are exported for the interpreter (Program itself needs to
|
||||||
// be exported in package "parser", otherwise these could live in
|
// be exported in package "parser", otherwise these could live in
|
||||||
// "internal/ast".)
|
// "internal/ast".)
|
||||||
Begin []Stmts
|
Begin []ast.Stmts
|
||||||
Actions []Action
|
Actions []ast.Action
|
||||||
End []Stmts
|
End []ast.Stmts
|
||||||
Functions []Function
|
Functions []ast.Function
|
||||||
Scalars map[string]int
|
Scalars map[string]int
|
||||||
Arrays map[string]int
|
Arrays map[string]int
|
||||||
|
Compiled *compiler.Program
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns an indented, pretty-printed version of the parsed
|
// String returns an indented, pretty-printed version of the parsed
|
||||||
// program.
|
// program.
|
||||||
func (p *Program) String() string {
|
func (p *Program) String() string {
|
||||||
parts := []string{}
|
return p.toAST().String()
|
||||||
for _, ss := range p.Begin {
|
}
|
||||||
parts = append(parts, "BEGIN {\n"+ss.String()+"}")
|
|
||||||
|
// Disassemble writes a human-readable form of the program's virtual machine
|
||||||
|
// instructions to writer.
|
||||||
|
func (p *Program) Disassemble(writer io.Writer) error {
|
||||||
|
return p.Compiled.Disassemble(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// toAST converts the *Program to an *ast.Program.
|
||||||
|
func (p *Program) toAST() *ast.Program {
|
||||||
|
return &ast.Program{
|
||||||
|
Begin: p.Begin,
|
||||||
|
Actions: p.Actions,
|
||||||
|
End: p.End,
|
||||||
|
Functions: p.Functions,
|
||||||
|
Scalars: p.Scalars,
|
||||||
|
Arrays: p.Arrays,
|
||||||
}
|
}
|
||||||
for _, a := range p.Actions {
|
|
||||||
parts = append(parts, a.String())
|
|
||||||
}
|
|
||||||
for _, ss := range p.End {
|
|
||||||
parts = append(parts, "END {\n"+ss.String()+"}")
|
|
||||||
}
|
|
||||||
for _, function := range p.Functions {
|
|
||||||
parts = append(parts, function.String())
|
|
||||||
}
|
|
||||||
return strings.Join(parts, "\n\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parser state
|
// Parser state
|
||||||
|
@ -123,7 +135,7 @@ type parser struct {
|
||||||
varTypes map[string]map[string]typeInfo // map of func name to var name to type
|
varTypes map[string]map[string]typeInfo // map of func name to var name to type
|
||||||
varRefs []varRef // all variable references (usually scalars)
|
varRefs []varRef // all variable references (usually scalars)
|
||||||
arrayRefs []arrayRef // all array references
|
arrayRefs []arrayRef // all array references
|
||||||
multiExprs map[*MultiExpr]Position // tracks comma-separated expressions
|
multiExprs map[*ast.MultiExpr]Position // tracks comma-separated expressions
|
||||||
|
|
||||||
// Function tracking
|
// Function tracking
|
||||||
functions map[string]int // map of function name to index
|
functions map[string]int // map of function name to index
|
||||||
|
@ -154,7 +166,7 @@ func (p *parser) program() *Program {
|
||||||
default:
|
default:
|
||||||
p.inAction = true
|
p.inAction = true
|
||||||
// Allow empty pattern, normal pattern, or range pattern
|
// Allow empty pattern, normal pattern, or range pattern
|
||||||
pattern := []Expr{}
|
pattern := []ast.Expr{}
|
||||||
if !p.matches(LBRACE, EOF) {
|
if !p.matches(LBRACE, EOF) {
|
||||||
pattern = append(pattern, p.expr())
|
pattern = append(pattern, p.expr())
|
||||||
}
|
}
|
||||||
|
@ -163,7 +175,7 @@ func (p *parser) program() *Program {
|
||||||
pattern = append(pattern, p.expr())
|
pattern = append(pattern, p.expr())
|
||||||
}
|
}
|
||||||
// Or an empty action (equivalent to { print $0 })
|
// Or an empty action (equivalent to { print $0 })
|
||||||
action := Action{pattern, nil}
|
action := ast.Action{pattern, nil}
|
||||||
if p.tok == LBRACE {
|
if p.tok == LBRACE {
|
||||||
action.Stmts = p.stmtsBrace()
|
action.Stmts = p.stmtsBrace()
|
||||||
}
|
}
|
||||||
|
@ -181,7 +193,7 @@ func (p *parser) program() *Program {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a list of statements.
|
// Parse a list of statements.
|
||||||
func (p *parser) stmts() Stmts {
|
func (p *parser) stmts() ast.Stmts {
|
||||||
switch p.tok {
|
switch p.tok {
|
||||||
case SEMICOLON:
|
case SEMICOLON:
|
||||||
// This is so things like this parse correctly:
|
// This is so things like this parse correctly:
|
||||||
|
@ -191,15 +203,15 @@ func (p *parser) stmts() Stmts {
|
||||||
case LBRACE:
|
case LBRACE:
|
||||||
return p.stmtsBrace()
|
return p.stmtsBrace()
|
||||||
default:
|
default:
|
||||||
return []Stmt{p.stmt()}
|
return []ast.Stmt{p.stmt()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a list of statements surrounded in {...} braces.
|
// Parse a list of statements surrounded in {...} braces.
|
||||||
func (p *parser) stmtsBrace() Stmts {
|
func (p *parser) stmtsBrace() ast.Stmts {
|
||||||
p.expect(LBRACE)
|
p.expect(LBRACE)
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
ss := []Stmt{}
|
ss := []ast.Stmt{}
|
||||||
for p.tok != RBRACE && p.tok != EOF {
|
for p.tok != RBRACE && p.tok != EOF {
|
||||||
ss = append(ss, p.stmt())
|
ss = append(ss, p.stmt())
|
||||||
}
|
}
|
||||||
|
@ -211,7 +223,7 @@ func (p *parser) stmtsBrace() Stmts {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a "simple" statement (eg: allowed in a for loop init clause).
|
// Parse a "simple" statement (eg: allowed in a for loop init clause).
|
||||||
func (p *parser) simpleStmt() Stmt {
|
func (p *parser) simpleStmt() ast.Stmt {
|
||||||
switch p.tok {
|
switch p.tok {
|
||||||
case PRINT, PRINTF:
|
case PRINT, PRINTF:
|
||||||
op := p.tok
|
op := p.tok
|
||||||
|
@ -219,31 +231,31 @@ func (p *parser) simpleStmt() Stmt {
|
||||||
args := p.exprList(p.printExpr)
|
args := p.exprList(p.printExpr)
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
// This allows parens around all the print args
|
// This allows parens around all the print args
|
||||||
if m, ok := args[0].(*MultiExpr); ok {
|
if m, ok := args[0].(*ast.MultiExpr); ok {
|
||||||
args = m.Exprs
|
args = m.Exprs
|
||||||
p.useMultiExpr(m)
|
p.useMultiExpr(m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
redirect := ILLEGAL
|
redirect := ILLEGAL
|
||||||
var dest Expr
|
var dest ast.Expr
|
||||||
if p.matches(GREATER, APPEND, PIPE) {
|
if p.matches(GREATER, APPEND, PIPE) {
|
||||||
redirect = p.tok
|
redirect = p.tok
|
||||||
p.next()
|
p.next()
|
||||||
dest = p.expr()
|
dest = p.expr()
|
||||||
}
|
}
|
||||||
if op == PRINT {
|
if op == PRINT {
|
||||||
return &PrintStmt{args, redirect, dest}
|
return &ast.PrintStmt{args, redirect, dest}
|
||||||
} else {
|
} else {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
panic(p.errorf("expected printf args, got none"))
|
panic(p.errorf("expected printf args, got none"))
|
||||||
}
|
}
|
||||||
return &PrintfStmt{args, redirect, dest}
|
return &ast.PrintfStmt{args, redirect, dest}
|
||||||
}
|
}
|
||||||
case DELETE:
|
case DELETE:
|
||||||
p.next()
|
p.next()
|
||||||
ref := p.arrayRef(p.val, p.pos)
|
ref := p.arrayRef(p.val, p.pos)
|
||||||
p.expect(NAME)
|
p.expect(NAME)
|
||||||
var index []Expr
|
var index []ast.Expr
|
||||||
if p.tok == LBRACKET {
|
if p.tok == LBRACKET {
|
||||||
p.next()
|
p.next()
|
||||||
index = p.exprList(p.expr)
|
index = p.exprList(p.expr)
|
||||||
|
@ -252,20 +264,20 @@ func (p *parser) simpleStmt() Stmt {
|
||||||
}
|
}
|
||||||
p.expect(RBRACKET)
|
p.expect(RBRACKET)
|
||||||
}
|
}
|
||||||
return &DeleteStmt{ref, index}
|
return &ast.DeleteStmt{ref, index}
|
||||||
case IF, FOR, WHILE, DO, BREAK, CONTINUE, NEXT, EXIT, RETURN:
|
case IF, FOR, WHILE, DO, BREAK, CONTINUE, NEXT, EXIT, RETURN:
|
||||||
panic(p.errorf("expected print/printf, delete, or expression"))
|
panic(p.errorf("expected print/printf, delete, or expression"))
|
||||||
default:
|
default:
|
||||||
return &ExprStmt{p.expr()}
|
return &ast.ExprStmt{p.expr()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse any top-level statement.
|
// Parse any top-level statement.
|
||||||
func (p *parser) stmt() Stmt {
|
func (p *parser) stmt() ast.Stmt {
|
||||||
for p.matches(SEMICOLON, NEWLINE) {
|
for p.matches(SEMICOLON, NEWLINE) {
|
||||||
p.next()
|
p.next()
|
||||||
}
|
}
|
||||||
var s Stmt
|
var s ast.Stmt
|
||||||
switch p.tok {
|
switch p.tok {
|
||||||
case IF:
|
case IF:
|
||||||
p.next()
|
p.next()
|
||||||
|
@ -275,13 +287,13 @@ func (p *parser) stmt() Stmt {
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
body := p.stmts()
|
body := p.stmts()
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
var elseBody Stmts
|
var elseBody ast.Stmts
|
||||||
if p.tok == ELSE {
|
if p.tok == ELSE {
|
||||||
p.next()
|
p.next()
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
elseBody = p.stmts()
|
elseBody = p.stmts()
|
||||||
}
|
}
|
||||||
s = &IfStmt{cond, body, elseBody}
|
s = &ast.IfStmt{cond, body, elseBody}
|
||||||
case FOR:
|
case FOR:
|
||||||
// Parse for statement, either "for in" or C-like for loop.
|
// Parse for statement, either "for in" or C-like for loop.
|
||||||
//
|
//
|
||||||
|
@ -292,7 +304,7 @@ func (p *parser) stmt() Stmt {
|
||||||
//
|
//
|
||||||
p.next()
|
p.next()
|
||||||
p.expect(LPAREN)
|
p.expect(LPAREN)
|
||||||
var pre Stmt
|
var pre ast.Stmt
|
||||||
if p.tok != SEMICOLON {
|
if p.tok != SEMICOLON {
|
||||||
pre = p.simpleStmt()
|
pre = p.simpleStmt()
|
||||||
}
|
}
|
||||||
|
@ -300,41 +312,41 @@ func (p *parser) stmt() Stmt {
|
||||||
// Match: for (var in array) body
|
// Match: for (var in array) body
|
||||||
p.next()
|
p.next()
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
exprStmt, ok := pre.(*ExprStmt)
|
exprStmt, ok := pre.(*ast.ExprStmt)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(p.errorf("expected 'for (var in array) ...'"))
|
panic(p.errorf("expected 'for (var in array) ...'"))
|
||||||
}
|
}
|
||||||
inExpr, ok := (exprStmt.Expr).(*InExpr)
|
inExpr, ok := exprStmt.Expr.(*ast.InExpr)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(p.errorf("expected 'for (var in array) ...'"))
|
panic(p.errorf("expected 'for (var in array) ...'"))
|
||||||
}
|
}
|
||||||
if len(inExpr.Index) != 1 {
|
if len(inExpr.Index) != 1 {
|
||||||
panic(p.errorf("expected 'for (var in array) ...'"))
|
panic(p.errorf("expected 'for (var in array) ...'"))
|
||||||
}
|
}
|
||||||
varExpr, ok := (inExpr.Index[0]).(*VarExpr)
|
varExpr, ok := inExpr.Index[0].(*ast.VarExpr)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(p.errorf("expected 'for (var in array) ...'"))
|
panic(p.errorf("expected 'for (var in array) ...'"))
|
||||||
}
|
}
|
||||||
body := p.loopStmts()
|
body := p.loopStmts()
|
||||||
s = &ForInStmt{varExpr, inExpr.Array, body}
|
s = &ast.ForInStmt{varExpr, inExpr.Array, body}
|
||||||
} else {
|
} else {
|
||||||
// Match: for ([pre]; [cond]; [post]) body
|
// Match: for ([pre]; [cond]; [post]) body
|
||||||
p.expect(SEMICOLON)
|
p.expect(SEMICOLON)
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
var cond Expr
|
var cond ast.Expr
|
||||||
if p.tok != SEMICOLON {
|
if p.tok != SEMICOLON {
|
||||||
cond = p.expr()
|
cond = p.expr()
|
||||||
}
|
}
|
||||||
p.expect(SEMICOLON)
|
p.expect(SEMICOLON)
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
var post Stmt
|
var post ast.Stmt
|
||||||
if p.tok != RPAREN {
|
if p.tok != RPAREN {
|
||||||
post = p.simpleStmt()
|
post = p.simpleStmt()
|
||||||
}
|
}
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
body := p.loopStmts()
|
body := p.loopStmts()
|
||||||
s = &ForStmt{pre, cond, post, body}
|
s = &ast.ForStmt{pre, cond, post, body}
|
||||||
}
|
}
|
||||||
case WHILE:
|
case WHILE:
|
||||||
p.next()
|
p.next()
|
||||||
|
@ -343,7 +355,7 @@ func (p *parser) stmt() Stmt {
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
body := p.loopStmts()
|
body := p.loopStmts()
|
||||||
s = &WhileStmt{cond, body}
|
s = &ast.WhileStmt{cond, body}
|
||||||
case DO:
|
case DO:
|
||||||
p.next()
|
p.next()
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
|
@ -352,45 +364,45 @@ func (p *parser) stmt() Stmt {
|
||||||
p.expect(LPAREN)
|
p.expect(LPAREN)
|
||||||
cond := p.expr()
|
cond := p.expr()
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
s = &DoWhileStmt{body, cond}
|
s = &ast.DoWhileStmt{body, cond}
|
||||||
case BREAK:
|
case BREAK:
|
||||||
if p.loopDepth == 0 {
|
if p.loopDepth == 0 {
|
||||||
panic(p.errorf("break must be inside a loop body"))
|
panic(p.errorf("break must be inside a loop body"))
|
||||||
}
|
}
|
||||||
p.next()
|
p.next()
|
||||||
s = &BreakStmt{}
|
s = &ast.BreakStmt{}
|
||||||
case CONTINUE:
|
case CONTINUE:
|
||||||
if p.loopDepth == 0 {
|
if p.loopDepth == 0 {
|
||||||
panic(p.errorf("continue must be inside a loop body"))
|
panic(p.errorf("continue must be inside a loop body"))
|
||||||
}
|
}
|
||||||
p.next()
|
p.next()
|
||||||
s = &ContinueStmt{}
|
s = &ast.ContinueStmt{}
|
||||||
case NEXT:
|
case NEXT:
|
||||||
if !p.inAction && p.funcName == "" {
|
if !p.inAction && p.funcName == "" {
|
||||||
panic(p.errorf("next can't be inside BEGIN or END"))
|
panic(p.errorf("next can't be inside BEGIN or END"))
|
||||||
}
|
}
|
||||||
p.next()
|
p.next()
|
||||||
s = &NextStmt{}
|
s = &ast.NextStmt{}
|
||||||
case EXIT:
|
case EXIT:
|
||||||
p.next()
|
p.next()
|
||||||
var status Expr
|
var status ast.Expr
|
||||||
if !p.matches(NEWLINE, SEMICOLON, RBRACE) {
|
if !p.matches(NEWLINE, SEMICOLON, RBRACE) {
|
||||||
status = p.expr()
|
status = p.expr()
|
||||||
}
|
}
|
||||||
s = &ExitStmt{status}
|
s = &ast.ExitStmt{status}
|
||||||
case RETURN:
|
case RETURN:
|
||||||
if p.funcName == "" {
|
if p.funcName == "" {
|
||||||
panic(p.errorf("return must be inside a function"))
|
panic(p.errorf("return must be inside a function"))
|
||||||
}
|
}
|
||||||
p.next()
|
p.next()
|
||||||
var value Expr
|
var value ast.Expr
|
||||||
if !p.matches(NEWLINE, SEMICOLON, RBRACE) {
|
if !p.matches(NEWLINE, SEMICOLON, RBRACE) {
|
||||||
value = p.expr()
|
value = p.expr()
|
||||||
}
|
}
|
||||||
s = &ReturnStmt{value}
|
s = &ast.ReturnStmt{value}
|
||||||
case LBRACE:
|
case LBRACE:
|
||||||
body := p.stmtsBrace()
|
body := p.stmtsBrace()
|
||||||
s = &BlockStmt{body}
|
s = &ast.BlockStmt{body}
|
||||||
default:
|
default:
|
||||||
s = p.simpleStmt()
|
s = p.simpleStmt()
|
||||||
}
|
}
|
||||||
|
@ -407,7 +419,7 @@ func (p *parser) stmt() Stmt {
|
||||||
|
|
||||||
// Same as stmts(), but tracks that we're in a loop (as break and
|
// Same as stmts(), but tracks that we're in a loop (as break and
|
||||||
// continue can only occur inside a loop).
|
// continue can only occur inside a loop).
|
||||||
func (p *parser) loopStmts() Stmts {
|
func (p *parser) loopStmts() ast.Stmts {
|
||||||
p.loopDepth++
|
p.loopDepth++
|
||||||
ss := p.stmts()
|
ss := p.stmts()
|
||||||
p.loopDepth--
|
p.loopDepth--
|
||||||
|
@ -417,7 +429,7 @@ func (p *parser) loopStmts() Stmts {
|
||||||
// Parse a function definition and body. As it goes, this resolves
|
// Parse a function definition and body. As it goes, this resolves
|
||||||
// the local variable indexes and tracks which parameters are array
|
// the local variable indexes and tracks which parameters are array
|
||||||
// parameters.
|
// parameters.
|
||||||
func (p *parser) function() Function {
|
func (p *parser) function() ast.Function {
|
||||||
if p.funcName != "" {
|
if p.funcName != "" {
|
||||||
// Should never actually get here (FUNCTION token is only
|
// Should never actually get here (FUNCTION token is only
|
||||||
// handled at the top level), but just in case.
|
// handled at the top level), but just in case.
|
||||||
|
@ -458,13 +470,13 @@ func (p *parser) function() Function {
|
||||||
p.stopFunction()
|
p.stopFunction()
|
||||||
p.locals = nil
|
p.locals = nil
|
||||||
|
|
||||||
return Function{name, params, nil, body}
|
return ast.Function{name, params, nil, body}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse expressions separated by commas: args to print[f] or user
|
// Parse expressions separated by commas: args to print[f] or user
|
||||||
// function call, or multi-dimensional index.
|
// function call, or multi-dimensional index.
|
||||||
func (p *parser) exprList(parse func() Expr) []Expr {
|
func (p *parser) exprList(parse func() ast.Expr) []ast.Expr {
|
||||||
exprs := []Expr{}
|
exprs := []ast.Expr{}
|
||||||
first := true
|
first := true
|
||||||
for !p.matches(NEWLINE, SEMICOLON, RBRACE, RBRACKET, RPAREN, GREATER, PIPE, APPEND) {
|
for !p.matches(NEWLINE, SEMICOLON, RBRACE, RBRACKET, RPAREN, GREATER, PIPE, APPEND) {
|
||||||
if !first {
|
if !first {
|
||||||
|
@ -484,41 +496,43 @@ func (p *parser) exprList(parse func() Expr) []Expr {
|
||||||
// which skips PIPE GETLINE and GREATER expressions.
|
// which skips PIPE GETLINE and GREATER expressions.
|
||||||
|
|
||||||
// Parse a single expression.
|
// Parse a single expression.
|
||||||
func (p *parser) expr() Expr { return p.getLine() }
|
func (p *parser) expr() ast.Expr { return p.getLine() }
|
||||||
func (p *parser) printExpr() Expr { return p._assign(p.printCond) }
|
func (p *parser) printExpr() ast.Expr { return p._assign(p.printCond) }
|
||||||
|
|
||||||
// Parse an "expr | getline [lvalue]" expression:
|
// Parse an "expr | getline [lvalue]" expression:
|
||||||
//
|
//
|
||||||
// assign [PIPE GETLINE [lvalue]]
|
// assign [PIPE GETLINE [lvalue]]
|
||||||
//
|
func (p *parser) getLine() ast.Expr {
|
||||||
func (p *parser) getLine() Expr {
|
|
||||||
expr := p._assign(p.cond)
|
expr := p._assign(p.cond)
|
||||||
if p.tok == PIPE {
|
if p.tok == PIPE {
|
||||||
p.next()
|
p.next()
|
||||||
p.expect(GETLINE)
|
p.expect(GETLINE)
|
||||||
target := p.optionalLValue()
|
target := p.optionalLValue()
|
||||||
return &GetlineExpr{expr, target, nil}
|
return &ast.GetlineExpr{expr, target, nil}
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse an = assignment expression:
|
// Parse an = assignment expression:
|
||||||
//
|
//
|
||||||
// lvalue [assign_op assign]
|
// lvalue [assign_op assign]
|
||||||
//
|
//
|
||||||
// An lvalue is a variable name, an array[expr] index expression, or
|
// An lvalue is a variable name, an array[expr] index expression, or
|
||||||
// an $expr field expression.
|
// an $expr field expression.
|
||||||
//
|
func (p *parser) _assign(higher func() ast.Expr) ast.Expr {
|
||||||
func (p *parser) _assign(higher func() Expr) Expr {
|
|
||||||
expr := higher()
|
expr := higher()
|
||||||
if IsLValue(expr) && p.matches(ASSIGN, ADD_ASSIGN, DIV_ASSIGN,
|
_, isNamedField := expr.(*ast.NamedFieldExpr)
|
||||||
|
if (isNamedField || ast.IsLValue(expr)) && p.matches(ASSIGN, ADD_ASSIGN, DIV_ASSIGN,
|
||||||
MOD_ASSIGN, MUL_ASSIGN, POW_ASSIGN, SUB_ASSIGN) {
|
MOD_ASSIGN, MUL_ASSIGN, POW_ASSIGN, SUB_ASSIGN) {
|
||||||
|
if isNamedField {
|
||||||
|
panic(p.errorf("assigning @ expression not supported"))
|
||||||
|
}
|
||||||
op := p.tok
|
op := p.tok
|
||||||
p.next()
|
p.next()
|
||||||
right := p._assign(higher)
|
right := p._assign(higher)
|
||||||
switch op {
|
switch op {
|
||||||
case ASSIGN:
|
case ASSIGN:
|
||||||
return &AssignExpr{expr, right}
|
return &ast.AssignExpr{expr, right}
|
||||||
case ADD_ASSIGN:
|
case ADD_ASSIGN:
|
||||||
op = ADD
|
op = ADD
|
||||||
case DIV_ASSIGN:
|
case DIV_ASSIGN:
|
||||||
|
@ -532,19 +546,18 @@ func (p *parser) _assign(higher func() Expr) Expr {
|
||||||
case SUB_ASSIGN:
|
case SUB_ASSIGN:
|
||||||
op = SUB
|
op = SUB
|
||||||
}
|
}
|
||||||
return &AugAssignExpr{expr, op, right}
|
return &ast.AugAssignExpr{expr, op, right}
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a ?: conditional expression:
|
// Parse a ?: conditional expression:
|
||||||
//
|
//
|
||||||
// or [QUESTION NEWLINE* cond COLON NEWLINE* cond]
|
// or [QUESTION NEWLINE* cond COLON NEWLINE* cond]
|
||||||
//
|
func (p *parser) cond() ast.Expr { return p._cond(p.or) }
|
||||||
func (p *parser) cond() Expr { return p._cond(p.or) }
|
func (p *parser) printCond() ast.Expr { return p._cond(p.printOr) }
|
||||||
func (p *parser) printCond() Expr { return p._cond(p.printOr) }
|
|
||||||
|
|
||||||
func (p *parser) _cond(higher func() Expr) Expr {
|
func (p *parser) _cond(higher func() ast.Expr) ast.Expr {
|
||||||
expr := higher()
|
expr := higher()
|
||||||
if p.tok == QUESTION {
|
if p.tok == QUESTION {
|
||||||
p.next()
|
p.next()
|
||||||
|
@ -553,156 +566,154 @@ func (p *parser) _cond(higher func() Expr) Expr {
|
||||||
p.expect(COLON)
|
p.expect(COLON)
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
f := p.expr()
|
f := p.expr()
|
||||||
return &CondExpr{expr, t, f}
|
return &ast.CondExpr{expr, t, f}
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse an || or expression:
|
// Parse an || or expression:
|
||||||
//
|
//
|
||||||
// and [OR NEWLINE* and] [OR NEWLINE* and] ...
|
// and [OR NEWLINE* and] [OR NEWLINE* and] ...
|
||||||
//
|
func (p *parser) or() ast.Expr { return p.binaryLeft(p.and, true, OR) }
|
||||||
func (p *parser) or() Expr { return p.binaryLeft(p.and, true, OR) }
|
func (p *parser) printOr() ast.Expr { return p.binaryLeft(p.printAnd, true, OR) }
|
||||||
func (p *parser) printOr() Expr { return p.binaryLeft(p.printAnd, true, OR) }
|
|
||||||
|
|
||||||
// Parse an && and expression:
|
// Parse an && and expression:
|
||||||
//
|
//
|
||||||
// in [AND NEWLINE* in] [AND NEWLINE* in] ...
|
// in [AND NEWLINE* in] [AND NEWLINE* in] ...
|
||||||
//
|
func (p *parser) and() ast.Expr { return p.binaryLeft(p.in, true, AND) }
|
||||||
func (p *parser) and() Expr { return p.binaryLeft(p.in, true, AND) }
|
func (p *parser) printAnd() ast.Expr { return p.binaryLeft(p.printIn, true, AND) }
|
||||||
func (p *parser) printAnd() Expr { return p.binaryLeft(p.printIn, true, AND) }
|
|
||||||
|
|
||||||
// Parse an "in" expression:
|
// Parse an "in" expression:
|
||||||
//
|
//
|
||||||
// match [IN NAME] [IN NAME] ...
|
// match [IN NAME] [IN NAME] ...
|
||||||
//
|
func (p *parser) in() ast.Expr { return p._in(p.match) }
|
||||||
func (p *parser) in() Expr { return p._in(p.match) }
|
func (p *parser) printIn() ast.Expr { return p._in(p.printMatch) }
|
||||||
func (p *parser) printIn() Expr { return p._in(p.printMatch) }
|
|
||||||
|
|
||||||
func (p *parser) _in(higher func() Expr) Expr {
|
func (p *parser) _in(higher func() ast.Expr) ast.Expr {
|
||||||
expr := higher()
|
expr := higher()
|
||||||
for p.tok == IN {
|
for p.tok == IN {
|
||||||
p.next()
|
p.next()
|
||||||
ref := p.arrayRef(p.val, p.pos)
|
ref := p.arrayRef(p.val, p.pos)
|
||||||
p.expect(NAME)
|
p.expect(NAME)
|
||||||
expr = &InExpr{[]Expr{expr}, ref}
|
expr = &ast.InExpr{[]ast.Expr{expr}, ref}
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a ~ match expression:
|
// Parse a ~ match expression:
|
||||||
//
|
//
|
||||||
// compare [MATCH|NOT_MATCH compare]
|
// compare [MATCH|NOT_MATCH compare]
|
||||||
//
|
func (p *parser) match() ast.Expr { return p._match(p.compare) }
|
||||||
func (p *parser) match() Expr { return p._match(p.compare) }
|
func (p *parser) printMatch() ast.Expr { return p._match(p.printCompare) }
|
||||||
func (p *parser) printMatch() Expr { return p._match(p.printCompare) }
|
|
||||||
|
|
||||||
func (p *parser) _match(higher func() Expr) Expr {
|
func (p *parser) _match(higher func() ast.Expr) ast.Expr {
|
||||||
expr := higher()
|
expr := higher()
|
||||||
if p.matches(MATCH, NOT_MATCH) {
|
if p.matches(MATCH, NOT_MATCH) {
|
||||||
op := p.tok
|
op := p.tok
|
||||||
p.next()
|
p.next()
|
||||||
right := p.regexStr(higher) // Not match() as these aren't associative
|
right := p.regexStr(higher) // Not match() as these aren't associative
|
||||||
return &BinaryExpr{expr, op, right}
|
return &ast.BinaryExpr{expr, op, right}
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a comparison expression:
|
// Parse a comparison expression:
|
||||||
//
|
//
|
||||||
// concat [EQUALS|NOT_EQUALS|LESS|LTE|GREATER|GTE concat]
|
// concat [EQUALS|NOT_EQUALS|LESS|LTE|GREATER|GTE concat]
|
||||||
//
|
func (p *parser) compare() ast.Expr { return p._compare(EQUALS, NOT_EQUALS, LESS, LTE, GTE, GREATER) }
|
||||||
func (p *parser) compare() Expr { return p._compare(EQUALS, NOT_EQUALS, LESS, LTE, GTE, GREATER) }
|
func (p *parser) printCompare() ast.Expr { return p._compare(EQUALS, NOT_EQUALS, LESS, LTE, GTE) }
|
||||||
func (p *parser) printCompare() Expr { return p._compare(EQUALS, NOT_EQUALS, LESS, LTE, GTE) }
|
|
||||||
|
|
||||||
func (p *parser) _compare(ops ...Token) Expr {
|
func (p *parser) _compare(ops ...Token) ast.Expr {
|
||||||
expr := p.concat()
|
expr := p.concat()
|
||||||
if p.matches(ops...) {
|
if p.matches(ops...) {
|
||||||
op := p.tok
|
op := p.tok
|
||||||
p.next()
|
p.next()
|
||||||
right := p.concat() // Not compare() as these aren't associative
|
right := p.concat() // Not compare() as these aren't associative
|
||||||
return &BinaryExpr{expr, op, right}
|
return &ast.BinaryExpr{expr, op, right}
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) concat() Expr {
|
func (p *parser) concat() ast.Expr {
|
||||||
expr := p.add()
|
expr := p.add()
|
||||||
for p.matches(DOLLAR, NOT, NAME, NUMBER, STRING, LPAREN, INCR, DECR) ||
|
for p.matches(DOLLAR, AT, NOT, NAME, NUMBER, STRING, LPAREN, INCR, DECR) ||
|
||||||
(p.tok >= FIRST_FUNC && p.tok <= LAST_FUNC) {
|
p.tok >= FIRST_FUNC && p.tok <= LAST_FUNC {
|
||||||
right := p.add()
|
right := p.add()
|
||||||
expr = &BinaryExpr{expr, CONCAT, right}
|
expr = &ast.BinaryExpr{expr, CONCAT, right}
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) add() Expr {
|
func (p *parser) add() ast.Expr {
|
||||||
return p.binaryLeft(p.mul, false, ADD, SUB)
|
return p.binaryLeft(p.mul, false, ADD, SUB)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) mul() Expr {
|
func (p *parser) mul() ast.Expr {
|
||||||
return p.binaryLeft(p.pow, false, MUL, DIV, MOD)
|
return p.binaryLeft(p.pow, false, MUL, DIV, MOD)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) pow() Expr {
|
func (p *parser) pow() ast.Expr {
|
||||||
// Note that pow (expr ^ expr) is right-associative
|
// Note that pow (expr ^ expr) is right-associative
|
||||||
expr := p.preIncr()
|
expr := p.preIncr()
|
||||||
if p.tok == POW {
|
if p.tok == POW {
|
||||||
p.next()
|
p.next()
|
||||||
right := p.pow()
|
right := p.pow()
|
||||||
return &BinaryExpr{expr, POW, right}
|
return &ast.BinaryExpr{expr, POW, right}
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) preIncr() Expr {
|
func (p *parser) preIncr() ast.Expr {
|
||||||
if p.tok == INCR || p.tok == DECR {
|
if p.tok == INCR || p.tok == DECR {
|
||||||
op := p.tok
|
op := p.tok
|
||||||
p.next()
|
p.next()
|
||||||
exprPos := p.pos
|
exprPos := p.pos
|
||||||
expr := p.preIncr()
|
expr := p.preIncr()
|
||||||
if !IsLValue(expr) {
|
if !ast.IsLValue(expr) {
|
||||||
panic(p.posErrorf(exprPos, "expected lvalue after ++ or --"))
|
panic(p.posErrorf(exprPos, "expected lvalue after ++ or --"))
|
||||||
}
|
}
|
||||||
return &IncrExpr{expr, op, true}
|
return &ast.IncrExpr{expr, op, true}
|
||||||
}
|
}
|
||||||
return p.postIncr()
|
return p.postIncr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) postIncr() Expr {
|
func (p *parser) postIncr() ast.Expr {
|
||||||
expr := p.primary()
|
expr := p.primary()
|
||||||
if (p.tok == INCR || p.tok == DECR) && IsLValue(expr) {
|
if (p.tok == INCR || p.tok == DECR) && ast.IsLValue(expr) {
|
||||||
op := p.tok
|
op := p.tok
|
||||||
p.next()
|
p.next()
|
||||||
return &IncrExpr{expr, op, false}
|
return &ast.IncrExpr{expr, op, false}
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) primary() Expr {
|
func (p *parser) primary() ast.Expr {
|
||||||
switch p.tok {
|
switch p.tok {
|
||||||
case NUMBER:
|
case NUMBER:
|
||||||
// AWK allows forms like "1.5e", but ParseFloat doesn't
|
// AWK allows forms like "1.5e", but ParseFloat doesn't
|
||||||
s := strings.TrimRight(p.val, "eE")
|
s := strings.TrimRight(p.val, "eE")
|
||||||
n, _ := strconv.ParseFloat(s, 64)
|
n, _ := strconv.ParseFloat(s, 64)
|
||||||
p.next()
|
p.next()
|
||||||
return &NumExpr{n}
|
return &ast.NumExpr{n}
|
||||||
case STRING:
|
case STRING:
|
||||||
s := p.val
|
s := p.val
|
||||||
p.next()
|
p.next()
|
||||||
return &StrExpr{s}
|
return &ast.StrExpr{s}
|
||||||
case DIV, DIV_ASSIGN:
|
case DIV, DIV_ASSIGN:
|
||||||
// If we get to DIV or DIV_ASSIGN as a primary expression,
|
// If we get to DIV or DIV_ASSIGN as a primary expression,
|
||||||
// it's actually a regex.
|
// it's actually a regex.
|
||||||
regex := p.nextRegex()
|
regex := p.nextRegex()
|
||||||
return &RegExpr{regex}
|
return &ast.RegExpr{regex}
|
||||||
case DOLLAR:
|
case DOLLAR:
|
||||||
p.next()
|
p.next()
|
||||||
return &FieldExpr{p.primary()}
|
return &ast.FieldExpr{p.primary()}
|
||||||
|
case AT:
|
||||||
|
p.next()
|
||||||
|
return &ast.NamedFieldExpr{p.primary()}
|
||||||
case NOT, ADD, SUB:
|
case NOT, ADD, SUB:
|
||||||
op := p.tok
|
op := p.tok
|
||||||
p.next()
|
p.next()
|
||||||
return &UnaryExpr{op, p.pow()}
|
return &ast.UnaryExpr{op, p.pow()}
|
||||||
case NAME:
|
case NAME:
|
||||||
name := p.val
|
name := p.val
|
||||||
namePos := p.pos
|
namePos := p.pos
|
||||||
|
@ -715,7 +726,7 @@ func (p *parser) primary() Expr {
|
||||||
panic(p.errorf("expected expression instead of ]"))
|
panic(p.errorf("expected expression instead of ]"))
|
||||||
}
|
}
|
||||||
p.expect(RBRACKET)
|
p.expect(RBRACKET)
|
||||||
return &IndexExpr{p.arrayRef(name, namePos), index}
|
return &ast.IndexExpr{p.arrayRef(name, namePos), index}
|
||||||
} else if p.tok == LPAREN && !p.lexer.HadSpace() {
|
} else if p.tok == LPAREN && !p.lexer.HadSpace() {
|
||||||
if p.locals[name] {
|
if p.locals[name] {
|
||||||
panic(p.errorf("can't call local variable %q as function", name))
|
panic(p.errorf("can't call local variable %q as function", name))
|
||||||
|
@ -743,7 +754,7 @@ func (p *parser) primary() Expr {
|
||||||
p.next()
|
p.next()
|
||||||
ref := p.arrayRef(p.val, p.pos)
|
ref := p.arrayRef(p.val, p.pos)
|
||||||
p.expect(NAME)
|
p.expect(NAME)
|
||||||
return &InExpr{exprs, ref}
|
return &ast.InExpr{exprs, ref}
|
||||||
}
|
}
|
||||||
// MultiExpr is used as a pseudo-expression for print[f] parsing.
|
// MultiExpr is used as a pseudo-expression for print[f] parsing.
|
||||||
return p.multiExpr(exprs, parenPos)
|
return p.multiExpr(exprs, parenPos)
|
||||||
|
@ -751,12 +762,12 @@ func (p *parser) primary() Expr {
|
||||||
case GETLINE:
|
case GETLINE:
|
||||||
p.next()
|
p.next()
|
||||||
target := p.optionalLValue()
|
target := p.optionalLValue()
|
||||||
var file Expr
|
var file ast.Expr
|
||||||
if p.tok == LESS {
|
if p.tok == LESS {
|
||||||
p.next()
|
p.next()
|
||||||
file = p.primary()
|
file = p.primary()
|
||||||
}
|
}
|
||||||
return &GetlineExpr{nil, target, file}
|
return &ast.GetlineExpr{nil, target, file}
|
||||||
// Below is the parsing of all the builtin function calls. We
|
// Below is the parsing of all the builtin function calls. We
|
||||||
// could unify these but several of them have special handling
|
// could unify these but several of them have special handling
|
||||||
// (array/lvalue/regex params, optional arguments, and so on).
|
// (array/lvalue/regex params, optional arguments, and so on).
|
||||||
|
@ -768,18 +779,18 @@ func (p *parser) primary() Expr {
|
||||||
regex := p.regexStr(p.expr)
|
regex := p.regexStr(p.expr)
|
||||||
p.commaNewlines()
|
p.commaNewlines()
|
||||||
repl := p.expr()
|
repl := p.expr()
|
||||||
args := []Expr{regex, repl}
|
args := []ast.Expr{regex, repl}
|
||||||
if p.tok == COMMA {
|
if p.tok == COMMA {
|
||||||
p.commaNewlines()
|
p.commaNewlines()
|
||||||
inPos := p.pos
|
inPos := p.pos
|
||||||
in := p.expr()
|
in := p.expr()
|
||||||
if !IsLValue(in) {
|
if !ast.IsLValue(in) {
|
||||||
panic(p.posErrorf(inPos, "3rd arg to sub/gsub must be lvalue"))
|
panic(p.posErrorf(inPos, "3rd arg to sub/gsub must be lvalue"))
|
||||||
}
|
}
|
||||||
args = append(args, in)
|
args = append(args, in)
|
||||||
}
|
}
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
return &CallExpr{op, args}
|
return &ast.CallExpr{op, args}
|
||||||
case F_SPLIT:
|
case F_SPLIT:
|
||||||
p.next()
|
p.next()
|
||||||
p.expect(LPAREN)
|
p.expect(LPAREN)
|
||||||
|
@ -787,13 +798,13 @@ func (p *parser) primary() Expr {
|
||||||
p.commaNewlines()
|
p.commaNewlines()
|
||||||
ref := p.arrayRef(p.val, p.pos)
|
ref := p.arrayRef(p.val, p.pos)
|
||||||
p.expect(NAME)
|
p.expect(NAME)
|
||||||
args := []Expr{str, ref}
|
args := []ast.Expr{str, ref}
|
||||||
if p.tok == COMMA {
|
if p.tok == COMMA {
|
||||||
p.commaNewlines()
|
p.commaNewlines()
|
||||||
args = append(args, p.regexStr(p.expr))
|
args = append(args, p.regexStr(p.expr))
|
||||||
}
|
}
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
return &CallExpr{F_SPLIT, args}
|
return &ast.CallExpr{F_SPLIT, args}
|
||||||
case F_MATCH:
|
case F_MATCH:
|
||||||
p.next()
|
p.next()
|
||||||
p.expect(LPAREN)
|
p.expect(LPAREN)
|
||||||
|
@ -801,24 +812,24 @@ func (p *parser) primary() Expr {
|
||||||
p.commaNewlines()
|
p.commaNewlines()
|
||||||
regex := p.regexStr(p.expr)
|
regex := p.regexStr(p.expr)
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
return &CallExpr{F_MATCH, []Expr{str, regex}}
|
return &ast.CallExpr{F_MATCH, []ast.Expr{str, regex}}
|
||||||
case F_RAND:
|
case F_RAND:
|
||||||
p.next()
|
p.next()
|
||||||
p.expect(LPAREN)
|
p.expect(LPAREN)
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
return &CallExpr{F_RAND, nil}
|
return &ast.CallExpr{F_RAND, nil}
|
||||||
case F_SRAND:
|
case F_SRAND:
|
||||||
p.next()
|
p.next()
|
||||||
p.expect(LPAREN)
|
p.expect(LPAREN)
|
||||||
var args []Expr
|
var args []ast.Expr
|
||||||
if p.tok != RPAREN {
|
if p.tok != RPAREN {
|
||||||
args = append(args, p.expr())
|
args = append(args, p.expr())
|
||||||
}
|
}
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
return &CallExpr{F_SRAND, args}
|
return &ast.CallExpr{F_SRAND, args}
|
||||||
case F_LENGTH:
|
case F_LENGTH:
|
||||||
p.next()
|
p.next()
|
||||||
var args []Expr
|
var args []ast.Expr
|
||||||
// AWK quirk: "length" is allowed to be called without parens
|
// AWK quirk: "length" is allowed to be called without parens
|
||||||
if p.tok == LPAREN {
|
if p.tok == LPAREN {
|
||||||
p.next()
|
p.next()
|
||||||
|
@ -827,39 +838,39 @@ func (p *parser) primary() Expr {
|
||||||
}
|
}
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
}
|
}
|
||||||
return &CallExpr{F_LENGTH, args}
|
return &ast.CallExpr{F_LENGTH, args}
|
||||||
case F_SUBSTR:
|
case F_SUBSTR:
|
||||||
p.next()
|
p.next()
|
||||||
p.expect(LPAREN)
|
p.expect(LPAREN)
|
||||||
str := p.expr()
|
str := p.expr()
|
||||||
p.commaNewlines()
|
p.commaNewlines()
|
||||||
start := p.expr()
|
start := p.expr()
|
||||||
args := []Expr{str, start}
|
args := []ast.Expr{str, start}
|
||||||
if p.tok == COMMA {
|
if p.tok == COMMA {
|
||||||
p.commaNewlines()
|
p.commaNewlines()
|
||||||
args = append(args, p.expr())
|
args = append(args, p.expr())
|
||||||
}
|
}
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
return &CallExpr{F_SUBSTR, args}
|
return &ast.CallExpr{F_SUBSTR, args}
|
||||||
case F_SPRINTF:
|
case F_SPRINTF:
|
||||||
p.next()
|
p.next()
|
||||||
p.expect(LPAREN)
|
p.expect(LPAREN)
|
||||||
args := []Expr{p.expr()}
|
args := []ast.Expr{p.expr()}
|
||||||
for p.tok == COMMA {
|
for p.tok == COMMA {
|
||||||
p.commaNewlines()
|
p.commaNewlines()
|
||||||
args = append(args, p.expr())
|
args = append(args, p.expr())
|
||||||
}
|
}
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
return &CallExpr{F_SPRINTF, args}
|
return &ast.CallExpr{F_SPRINTF, args}
|
||||||
case F_FFLUSH:
|
case F_FFLUSH:
|
||||||
p.next()
|
p.next()
|
||||||
p.expect(LPAREN)
|
p.expect(LPAREN)
|
||||||
var args []Expr
|
var args []ast.Expr
|
||||||
if p.tok != RPAREN {
|
if p.tok != RPAREN {
|
||||||
args = append(args, p.expr())
|
args = append(args, p.expr())
|
||||||
}
|
}
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
return &CallExpr{F_FFLUSH, args}
|
return &ast.CallExpr{F_FFLUSH, args}
|
||||||
case F_COS, F_SIN, F_EXP, F_LOG, F_SQRT, F_INT, F_TOLOWER, F_TOUPPER, F_SYSTEM, F_CLOSE:
|
case F_COS, F_SIN, F_EXP, F_LOG, F_SQRT, F_INT, F_TOLOWER, F_TOUPPER, F_SYSTEM, F_CLOSE:
|
||||||
// Simple 1-argument functions
|
// Simple 1-argument functions
|
||||||
op := p.tok
|
op := p.tok
|
||||||
|
@ -867,7 +878,7 @@ func (p *parser) primary() Expr {
|
||||||
p.expect(LPAREN)
|
p.expect(LPAREN)
|
||||||
arg := p.expr()
|
arg := p.expr()
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
return &CallExpr{op, []Expr{arg}}
|
return &ast.CallExpr{op, []ast.Expr{arg}}
|
||||||
case F_ATAN2, F_INDEX:
|
case F_ATAN2, F_INDEX:
|
||||||
// Simple 2-argument functions
|
// Simple 2-argument functions
|
||||||
op := p.tok
|
op := p.tok
|
||||||
|
@ -877,14 +888,14 @@ func (p *parser) primary() Expr {
|
||||||
p.commaNewlines()
|
p.commaNewlines()
|
||||||
arg2 := p.expr()
|
arg2 := p.expr()
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
return &CallExpr{op, []Expr{arg1, arg2}}
|
return &ast.CallExpr{op, []ast.Expr{arg1, arg2}}
|
||||||
default:
|
default:
|
||||||
panic(p.errorf("expected expression instead of %s", p.tok))
|
panic(p.errorf("expected expression instead of %s", p.tok))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse an optional lvalue
|
// Parse an optional lvalue
|
||||||
func (p *parser) optionalLValue() Expr {
|
func (p *parser) optionalLValue() ast.Expr {
|
||||||
switch p.tok {
|
switch p.tok {
|
||||||
case NAME:
|
case NAME:
|
||||||
if p.lexer.PeekByte() == '(' {
|
if p.lexer.PeekByte() == '(' {
|
||||||
|
@ -902,12 +913,12 @@ func (p *parser) optionalLValue() Expr {
|
||||||
panic(p.errorf("expected expression instead of ]"))
|
panic(p.errorf("expected expression instead of ]"))
|
||||||
}
|
}
|
||||||
p.expect(RBRACKET)
|
p.expect(RBRACKET)
|
||||||
return &IndexExpr{p.arrayRef(name, namePos), index}
|
return &ast.IndexExpr{p.arrayRef(name, namePos), index}
|
||||||
}
|
}
|
||||||
return p.varRef(name, namePos)
|
return p.varRef(name, namePos)
|
||||||
case DOLLAR:
|
case DOLLAR:
|
||||||
p.next()
|
p.next()
|
||||||
return &FieldExpr{p.primary()}
|
return &ast.FieldExpr{p.primary()}
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -915,12 +926,11 @@ func (p *parser) optionalLValue() Expr {
|
||||||
|
|
||||||
// Parse /.../ regex or generic expression:
|
// Parse /.../ regex or generic expression:
|
||||||
//
|
//
|
||||||
// REGEX | expr
|
// REGEX | expr
|
||||||
//
|
func (p *parser) regexStr(parse func() ast.Expr) ast.Expr {
|
||||||
func (p *parser) regexStr(parse func() Expr) Expr {
|
|
||||||
if p.matches(DIV, DIV_ASSIGN) {
|
if p.matches(DIV, DIV_ASSIGN) {
|
||||||
regex := p.nextRegex()
|
regex := p.nextRegex()
|
||||||
return &StrExpr{regex}
|
return &ast.StrExpr{regex}
|
||||||
}
|
}
|
||||||
return parse()
|
return parse()
|
||||||
}
|
}
|
||||||
|
@ -928,9 +938,8 @@ func (p *parser) regexStr(parse func() Expr) Expr {
|
||||||
// Parse left-associative binary operator. Allow newlines after
|
// Parse left-associative binary operator. Allow newlines after
|
||||||
// operator if allowNewline is true.
|
// operator if allowNewline is true.
|
||||||
//
|
//
|
||||||
// parse [op parse] [op parse] ...
|
// parse [op parse] [op parse] ...
|
||||||
//
|
func (p *parser) binaryLeft(higher func() ast.Expr, allowNewline bool, ops ...Token) ast.Expr {
|
||||||
func (p *parser) binaryLeft(higher func() Expr, allowNewline bool, ops ...Token) Expr {
|
|
||||||
expr := higher()
|
expr := higher()
|
||||||
for p.matches(ops...) {
|
for p.matches(ops...) {
|
||||||
op := p.tok
|
op := p.tok
|
||||||
|
@ -939,15 +948,14 @@ func (p *parser) binaryLeft(higher func() Expr, allowNewline bool, ops ...Token)
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
}
|
}
|
||||||
right := higher()
|
right := higher()
|
||||||
expr = &BinaryExpr{expr, op, right}
|
expr = &ast.BinaryExpr{expr, op, right}
|
||||||
}
|
}
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse comma followed by optional newlines:
|
// Parse comma followed by optional newlines:
|
||||||
//
|
//
|
||||||
// COMMA NEWLINE*
|
// COMMA NEWLINE*
|
||||||
//
|
|
||||||
func (p *parser) commaNewlines() {
|
func (p *parser) commaNewlines() {
|
||||||
p.expect(COMMA)
|
p.expect(COMMA)
|
||||||
p.optionalNewlines()
|
p.optionalNewlines()
|
||||||
|
@ -955,8 +963,7 @@ func (p *parser) commaNewlines() {
|
||||||
|
|
||||||
// Parse zero or more optional newlines:
|
// Parse zero or more optional newlines:
|
||||||
//
|
//
|
||||||
// [NEWLINE] [NEWLINE] ...
|
// [NEWLINE] [NEWLINE] ...
|
||||||
//
|
|
||||||
func (p *parser) optionalNewlines() {
|
func (p *parser) optionalNewlines() {
|
||||||
for p.tok == NEWLINE {
|
for p.tok == NEWLINE {
|
||||||
p.next()
|
p.next()
|
||||||
|
@ -1021,9 +1028,9 @@ func (p *parser) posErrorf(pos Position, format string, args ...interface{}) err
|
||||||
|
|
||||||
// Parse call to a user-defined function (and record call site for
|
// Parse call to a user-defined function (and record call site for
|
||||||
// resolving later).
|
// resolving later).
|
||||||
func (p *parser) userCall(name string, pos Position) *UserCallExpr {
|
func (p *parser) userCall(name string, pos Position) *ast.UserCallExpr {
|
||||||
p.expect(LPAREN)
|
p.expect(LPAREN)
|
||||||
args := []Expr{}
|
args := []ast.Expr{}
|
||||||
i := 0
|
i := 0
|
||||||
for !p.matches(NEWLINE, RPAREN) {
|
for !p.matches(NEWLINE, RPAREN) {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
|
@ -1035,7 +1042,7 @@ func (p *parser) userCall(name string, pos Position) *UserCallExpr {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
p.expect(RPAREN)
|
p.expect(RPAREN)
|
||||||
call := &UserCallExpr{false, -1, name, args} // index is resolved later
|
call := &ast.UserCallExpr{false, -1, name, args} // index is resolved later
|
||||||
p.recordUserCall(call, pos)
|
p.recordUserCall(call, pos)
|
||||||
return call
|
return call
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
. "github.com/benhoyt/goawk/internal/ast"
|
"github.com/benhoyt/goawk/internal/ast"
|
||||||
. "github.com/benhoyt/goawk/lexer"
|
. "github.com/benhoyt/goawk/lexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,8 +33,8 @@ func (t varType) String() string {
|
||||||
// typeInfo records type information for a single variable
|
// typeInfo records type information for a single variable
|
||||||
type typeInfo struct {
|
type typeInfo struct {
|
||||||
typ varType
|
typ varType
|
||||||
ref *VarExpr
|
ref *ast.VarExpr
|
||||||
scope VarScope
|
scope ast.VarScope
|
||||||
index int
|
index int
|
||||||
callName string
|
callName string
|
||||||
argIndex int
|
argIndex int
|
||||||
|
@ -44,9 +44,9 @@ type typeInfo struct {
|
||||||
func (t typeInfo) String() string {
|
func (t typeInfo) String() string {
|
||||||
var scope string
|
var scope string
|
||||||
switch t.scope {
|
switch t.scope {
|
||||||
case ScopeGlobal:
|
case ast.ScopeGlobal:
|
||||||
scope = "Global"
|
scope = "Global"
|
||||||
case ScopeLocal:
|
case ast.ScopeLocal:
|
||||||
scope = "Local"
|
scope = "Local"
|
||||||
default:
|
default:
|
||||||
scope = "Special"
|
scope = "Special"
|
||||||
|
@ -58,7 +58,7 @@ func (t typeInfo) String() string {
|
||||||
// A single variable reference (normally scalar)
|
// A single variable reference (normally scalar)
|
||||||
type varRef struct {
|
type varRef struct {
|
||||||
funcName string
|
funcName string
|
||||||
ref *VarExpr
|
ref *ast.VarExpr
|
||||||
isArg bool
|
isArg bool
|
||||||
pos Position
|
pos Position
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ type varRef struct {
|
||||||
// A single array reference
|
// A single array reference
|
||||||
type arrayRef struct {
|
type arrayRef struct {
|
||||||
funcName string
|
funcName string
|
||||||
ref *ArrayExpr
|
ref *ast.ArrayExpr
|
||||||
pos Position
|
pos Position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,8 +76,9 @@ func (p *parser) initResolve() {
|
||||||
p.varTypes[""] = make(map[string]typeInfo) // globals
|
p.varTypes[""] = make(map[string]typeInfo) // globals
|
||||||
p.functions = make(map[string]int)
|
p.functions = make(map[string]int)
|
||||||
p.arrayRef("ARGV", Position{1, 1}) // interpreter relies on ARGV being present
|
p.arrayRef("ARGV", Position{1, 1}) // interpreter relies on ARGV being present
|
||||||
p.arrayRef("ENVIRON", Position{1, 1}) // and ENVIRON
|
p.arrayRef("ENVIRON", Position{1, 1}) // and other built-in arrays
|
||||||
p.multiExprs = make(map[*MultiExpr]Position, 3)
|
p.arrayRef("FIELDS", Position{1, 1})
|
||||||
|
p.multiExprs = make(map[*ast.MultiExpr]Position, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signal the start of a function
|
// Signal the start of a function
|
||||||
|
@ -98,13 +99,13 @@ func (p *parser) addFunction(name string, index int) {
|
||||||
|
|
||||||
// Records a call to a user function (for resolving indexes later)
|
// Records a call to a user function (for resolving indexes later)
|
||||||
type userCall struct {
|
type userCall struct {
|
||||||
call *UserCallExpr
|
call *ast.UserCallExpr
|
||||||
pos Position
|
pos Position
|
||||||
inFunc string
|
inFunc string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record a user call site
|
// Record a user call site
|
||||||
func (p *parser) recordUserCall(call *UserCallExpr, pos Position) {
|
func (p *parser) recordUserCall(call *ast.UserCallExpr, pos Position) {
|
||||||
p.userCalls = append(p.userCalls, userCall{call, pos, p.funcName})
|
p.userCalls = append(p.userCalls, userCall{call, pos, p.funcName})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,8 +150,8 @@ func (p *parser) resolveUserCalls(prog *Program) {
|
||||||
|
|
||||||
// For arguments that are variable references, we don't know the
|
// For arguments that are variable references, we don't know the
|
||||||
// type based on context, so mark the types for these as unknown.
|
// type based on context, so mark the types for these as unknown.
|
||||||
func (p *parser) processUserCallArg(funcName string, arg Expr, index int) {
|
func (p *parser) processUserCallArg(funcName string, arg ast.Expr, index int) {
|
||||||
if varExpr, ok := arg.(*VarExpr); ok {
|
if varExpr, ok := arg.(*ast.VarExpr); ok {
|
||||||
scope, varFuncName := p.getScope(varExpr.Name)
|
scope, varFuncName := p.getScope(varExpr.Name)
|
||||||
ref := p.varTypes[varFuncName][varExpr.Name].ref
|
ref := p.varTypes[varFuncName][varExpr.Name].ref
|
||||||
if ref == varExpr {
|
if ref == varExpr {
|
||||||
|
@ -166,22 +167,22 @@ func (p *parser) processUserCallArg(funcName string, arg Expr, index int) {
|
||||||
|
|
||||||
// Determine scope of given variable reference (and funcName if it's
|
// Determine scope of given variable reference (and funcName if it's
|
||||||
// a local, otherwise empty string)
|
// a local, otherwise empty string)
|
||||||
func (p *parser) getScope(name string) (VarScope, string) {
|
func (p *parser) getScope(name string) (ast.VarScope, string) {
|
||||||
switch {
|
switch {
|
||||||
case p.locals[name]:
|
case p.locals[name]:
|
||||||
return ScopeLocal, p.funcName
|
return ast.ScopeLocal, p.funcName
|
||||||
case SpecialVarIndex(name) > 0:
|
case ast.SpecialVarIndex(name) > 0:
|
||||||
return ScopeSpecial, ""
|
return ast.ScopeSpecial, ""
|
||||||
default:
|
default:
|
||||||
return ScopeGlobal, ""
|
return ast.ScopeGlobal, ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record a variable (scalar) reference and return the *VarExpr (but
|
// Record a variable (scalar) reference and return the *VarExpr (but
|
||||||
// VarExpr.Index won't be set till later)
|
// VarExpr.Index won't be set till later)
|
||||||
func (p *parser) varRef(name string, pos Position) *VarExpr {
|
func (p *parser) varRef(name string, pos Position) *ast.VarExpr {
|
||||||
scope, funcName := p.getScope(name)
|
scope, funcName := p.getScope(name)
|
||||||
expr := &VarExpr{scope, 0, name}
|
expr := &ast.VarExpr{scope, 0, name}
|
||||||
p.varRefs = append(p.varRefs, varRef{funcName, expr, false, pos})
|
p.varRefs = append(p.varRefs, varRef{funcName, expr, false, pos})
|
||||||
info := p.varTypes[funcName][name]
|
info := p.varTypes[funcName][name]
|
||||||
if info.typ == typeUnknown {
|
if info.typ == typeUnknown {
|
||||||
|
@ -192,12 +193,12 @@ func (p *parser) varRef(name string, pos Position) *VarExpr {
|
||||||
|
|
||||||
// Record an array reference and return the *ArrayExpr (but
|
// Record an array reference and return the *ArrayExpr (but
|
||||||
// ArrayExpr.Index won't be set till later)
|
// ArrayExpr.Index won't be set till later)
|
||||||
func (p *parser) arrayRef(name string, pos Position) *ArrayExpr {
|
func (p *parser) arrayRef(name string, pos Position) *ast.ArrayExpr {
|
||||||
scope, funcName := p.getScope(name)
|
scope, funcName := p.getScope(name)
|
||||||
if scope == ScopeSpecial {
|
if scope == ast.ScopeSpecial {
|
||||||
panic(p.errorf("can't use scalar %q as array", name))
|
panic(p.errorf("can't use scalar %q as array", name))
|
||||||
}
|
}
|
||||||
expr := &ArrayExpr{scope, 0, name}
|
expr := &ast.ArrayExpr{scope, 0, name}
|
||||||
p.arrayRefs = append(p.arrayRefs, arrayRef{funcName, expr, pos})
|
p.arrayRefs = append(p.arrayRefs, arrayRef{funcName, expr, pos})
|
||||||
info := p.varTypes[funcName][name]
|
info := p.varTypes[funcName][name]
|
||||||
if info.typ == typeUnknown {
|
if info.typ == typeUnknown {
|
||||||
|
@ -233,52 +234,45 @@ func (p *parser) printVarTypes(prog *Program) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we can't finish resolving after this many iterations, give up
|
|
||||||
const maxResolveIterations = 10000
|
|
||||||
|
|
||||||
// Resolve unknown variables types and generate variable indexes and
|
// Resolve unknown variables types and generate variable indexes and
|
||||||
// name-to-index mappings for interpreter
|
// name-to-index mappings for interpreter
|
||||||
func (p *parser) resolveVars(prog *Program) {
|
func (p *parser) resolveVars(prog *Program) {
|
||||||
// First go through all unknown types and try to determine the
|
// First go through all unknown types and try to determine the
|
||||||
// type from the parameter type in that function definition. May
|
// type from the parameter type in that function definition.
|
||||||
// need multiple passes depending on the order of functions. This
|
// Iterate through functions in topological order, for example
|
||||||
// is not particularly efficient, but on realistic programs it's
|
// if f() calls g(), process g first, then f.
|
||||||
// not an issue.
|
callGraph := make(map[string]map[string]struct{})
|
||||||
for i := 0; ; i++ {
|
for _, call := range p.userCalls {
|
||||||
progressed := false
|
if _, ok := callGraph[call.inFunc]; !ok {
|
||||||
for funcName, infos := range p.varTypes {
|
callGraph[call.inFunc] = make(map[string]struct{})
|
||||||
for name, info := range infos {
|
}
|
||||||
if info.scope == ScopeSpecial || info.typ != typeUnknown {
|
callGraph[call.inFunc][call.call.Name] = struct{}{}
|
||||||
// It's a special var or type is already known
|
}
|
||||||
continue
|
sortedFuncs := topoSort(callGraph)
|
||||||
}
|
for _, funcName := range sortedFuncs {
|
||||||
funcIndex, ok := p.functions[info.callName]
|
infos := p.varTypes[funcName]
|
||||||
if !ok {
|
for name, info := range infos {
|
||||||
// Function being called is a native function
|
if info.scope == ast.ScopeSpecial || info.typ != typeUnknown {
|
||||||
continue
|
// It's a special var or type is already known
|
||||||
}
|
continue
|
||||||
// Determine var type based on type of this parameter
|
}
|
||||||
// in the called function (if we know that)
|
funcIndex, ok := p.functions[info.callName]
|
||||||
paramName := prog.Functions[funcIndex].Params[info.argIndex]
|
if !ok {
|
||||||
typ := p.varTypes[info.callName][paramName].typ
|
// Function being called is a native function
|
||||||
if typ != typeUnknown {
|
continue
|
||||||
if p.debugTypes {
|
}
|
||||||
fmt.Fprintf(p.debugWriter, "resolving %s:%s to %s\n",
|
// Determine var type based on type of this parameter
|
||||||
funcName, name, typ)
|
// in the called function (if we know that)
|
||||||
}
|
paramName := prog.Functions[funcIndex].Params[info.argIndex]
|
||||||
info.typ = typ
|
typ := p.varTypes[info.callName][paramName].typ
|
||||||
p.varTypes[funcName][name] = info
|
if typ != typeUnknown {
|
||||||
progressed = true
|
if p.debugTypes {
|
||||||
}
|
fmt.Fprintf(p.debugWriter, "resolving %s:%s to %s\n",
|
||||||
|
funcName, name, typ)
|
||||||
|
}
|
||||||
|
info.typ = typ
|
||||||
|
p.varTypes[funcName][name] = info
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if !progressed {
|
|
||||||
// If we didn't progress we're done (or trying again is
|
|
||||||
// not going to help)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if i >= maxResolveIterations {
|
|
||||||
panic(p.errorf("too many iterations trying to resolve variable types"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,8 +287,8 @@ func (p *parser) resolveVars(prog *Program) {
|
||||||
panic(p.errorf("global var %q can't also be a function", name))
|
panic(p.errorf("global var %q can't also be a function", name))
|
||||||
}
|
}
|
||||||
var index int
|
var index int
|
||||||
if info.scope == ScopeSpecial {
|
if info.scope == ast.ScopeSpecial {
|
||||||
index = SpecialVarIndex(name)
|
index = ast.SpecialVarIndex(name)
|
||||||
} else if info.typ == typeArray {
|
} else if info.typ == typeArray {
|
||||||
index = len(prog.Arrays)
|
index = len(prog.Arrays)
|
||||||
prog.Arrays[name] = index
|
prog.Arrays[name] = index
|
||||||
|
@ -317,7 +311,7 @@ func (p *parser) resolveVars(prog *Program) {
|
||||||
}
|
}
|
||||||
function := prog.Functions[c.call.Index]
|
function := prog.Functions[c.call.Index]
|
||||||
for i, arg := range c.call.Args {
|
for i, arg := range c.call.Args {
|
||||||
varExpr, ok := arg.(*VarExpr)
|
varExpr, ok := arg.(*ast.VarExpr)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -368,7 +362,7 @@ func (p *parser) resolveVars(prog *Program) {
|
||||||
// Check native function calls
|
// Check native function calls
|
||||||
if c.call.Native {
|
if c.call.Native {
|
||||||
for _, arg := range c.call.Args {
|
for _, arg := range c.call.Args {
|
||||||
varExpr, ok := arg.(*VarExpr)
|
varExpr, ok := arg.(*ast.VarExpr)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Non-variable expression, must be scalar
|
// Non-variable expression, must be scalar
|
||||||
continue
|
continue
|
||||||
|
@ -385,7 +379,7 @@ func (p *parser) resolveVars(prog *Program) {
|
||||||
// Check AWK function calls
|
// Check AWK function calls
|
||||||
function := prog.Functions[c.call.Index]
|
function := prog.Functions[c.call.Index]
|
||||||
for i, arg := range c.call.Args {
|
for i, arg := range c.call.Args {
|
||||||
varExpr, ok := arg.(*VarExpr)
|
varExpr, ok := arg.(*ast.VarExpr)
|
||||||
if !ok {
|
if !ok {
|
||||||
if function.Arrays[i] {
|
if function.Arrays[i] {
|
||||||
panic(p.posErrorf(c.pos, "can't pass scalar %s as array param", arg))
|
panic(p.posErrorf(c.pos, "can't pass scalar %s as array param", arg))
|
||||||
|
@ -441,14 +435,14 @@ func (p *parser) getVarFuncName(prog *Program, name, inFunc string) string {
|
||||||
|
|
||||||
// Record a "multi expression" (comma-separated pseudo-expression
|
// Record a "multi expression" (comma-separated pseudo-expression
|
||||||
// used to allow commas around print/printf arguments).
|
// used to allow commas around print/printf arguments).
|
||||||
func (p *parser) multiExpr(exprs []Expr, pos Position) Expr {
|
func (p *parser) multiExpr(exprs []ast.Expr, pos Position) ast.Expr {
|
||||||
expr := &MultiExpr{exprs}
|
expr := &ast.MultiExpr{exprs}
|
||||||
p.multiExprs[expr] = pos
|
p.multiExprs[expr] = pos
|
||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the multi expression as used (by a print/printf statement).
|
// Mark the multi expression as used (by a print/printf statement).
|
||||||
func (p *parser) useMultiExpr(expr *MultiExpr) {
|
func (p *parser) useMultiExpr(expr *ast.MultiExpr) {
|
||||||
delete(p.multiExprs, expr)
|
delete(p.multiExprs, expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,7 +454,7 @@ func (p *parser) checkMultiExprs() {
|
||||||
// Show error on first comma-separated expression
|
// Show error on first comma-separated expression
|
||||||
min := Position{1000000000, 1000000000}
|
min := Position{1000000000, 1000000000}
|
||||||
for _, pos := range p.multiExprs {
|
for _, pos := range p.multiExprs {
|
||||||
if pos.Line < min.Line || (pos.Line == min.Line && pos.Column < min.Column) {
|
if pos.Line < min.Line || pos.Line == min.Line && pos.Column < min.Column {
|
||||||
min = pos
|
min = pos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Topological sorting
|
||||||
|
|
||||||
|
package parser
|
||||||
|
|
||||||
|
/*
|
||||||
|
This algorithm is taken from:
|
||||||
|
https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
|
||||||
|
|
||||||
|
L ← Empty list that will contain the sorted nodes
|
||||||
|
while exists nodes without a permanent mark do
|
||||||
|
select an unmarked node n
|
||||||
|
visit(n)
|
||||||
|
|
||||||
|
function visit(node n)
|
||||||
|
if n has a permanent mark then
|
||||||
|
return
|
||||||
|
if n has a temporary mark then
|
||||||
|
stop (not a DAG)
|
||||||
|
|
||||||
|
mark n with a temporary mark
|
||||||
|
|
||||||
|
for each node m with an edge from n to m do
|
||||||
|
visit(m)
|
||||||
|
|
||||||
|
remove temporary mark from n
|
||||||
|
mark n with a permanent mark
|
||||||
|
add n to head of L
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Perform a topological sort on the given graph.
|
||||||
|
func topoSort(graph map[string]map[string]struct{}) []string {
|
||||||
|
if len(graph) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
unmarked := make(map[string]struct{})
|
||||||
|
for node := range graph {
|
||||||
|
unmarked[node] = struct{}{}
|
||||||
|
}
|
||||||
|
permMarks := make(map[string]struct{})
|
||||||
|
tempMarks := make(map[string]struct{})
|
||||||
|
var sorted []string
|
||||||
|
|
||||||
|
var visit func(string)
|
||||||
|
visit = func(n string) {
|
||||||
|
if _, ok := permMarks[n]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := tempMarks[n]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tempMarks[n] = struct{}{}
|
||||||
|
for m := range graph[n] {
|
||||||
|
visit(m)
|
||||||
|
}
|
||||||
|
delete(tempMarks, n)
|
||||||
|
permMarks[n] = struct{}{}
|
||||||
|
delete(unmarked, n)
|
||||||
|
sorted = append(sorted, n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(unmarked) > 0 {
|
||||||
|
var n string
|
||||||
|
for n = range unmarked {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
visit(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
# github.com/benhoyt/goawk v1.13.0
|
# github.com/benhoyt/goawk v1.20.0
|
||||||
## explicit; go 1.13
|
## explicit; go 1.14
|
||||||
github.com/benhoyt/goawk/internal/ast
|
github.com/benhoyt/goawk/internal/ast
|
||||||
|
github.com/benhoyt/goawk/internal/compiler
|
||||||
github.com/benhoyt/goawk/interp
|
github.com/benhoyt/goawk/interp
|
||||||
github.com/benhoyt/goawk/lexer
|
github.com/benhoyt/goawk/lexer
|
||||||
github.com/benhoyt/goawk/parser
|
github.com/benhoyt/goawk/parser
|
||||||
|
|
Loading…
Reference in New Issue