Skip to content

Commit 08c8242

Browse files
authored
Add support for "getline lvalue" (#85)
Parsing getline is tricky, and I needed to add a PeekByte function to the lexer so the parser can tell the difference between "getline foo" and "getline foo()". There are still a few arcane forms not supported by GoAWK (see commented-out interp tests), but most forms are supported now. Also ensure data returned by getline is treated as numeric string.
1 parent 72cc509 commit 08c8242

9 files changed

Lines changed: 159 additions & 77 deletions

File tree

goawk_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,10 @@ func sortedLines(data []byte) []byte {
218218

219219
func TestGAWK(t *testing.T) {
220220
skip := map[string]bool{ // TODO: fix these
221-
"inputred": true, // getInputScanner errors
222-
223221
"getline": true, // getline syntax issues (may be okay, see grammar notes at http://pubs.opengroup.org/onlinepubs/007904975/utilities/awk.html#tag_04_06_13_14)
224222
"getline3": true, // getline syntax issues (similar to above)
225223
"getline5": true, // getline syntax issues (similar to above)
224+
"inputred": true, // getline syntax issues (similar to above)
226225

227226
"gsubtst7": true, // something wrong with gsub or field split/join
228227
"splitwht": true, // other awks handle split(s, a, " ") differently from split(s, a, / /)

internal/ast/ast.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ func (e *MultiExpr) String() string {
299299
// GetlineExpr is an expression read from file or pipe input.
300300
type GetlineExpr struct {
301301
Command Expr
302-
Var *VarExpr
302+
Target Expr
303303
File Expr
304304
}
305305

@@ -309,8 +309,8 @@ func (e *GetlineExpr) String() string {
309309
s += e.Command.String() + " |"
310310
}
311311
s += "getline"
312-
if e.Var != nil {
313-
s += " " + e.Var.String()
312+
if e.Target != nil {
313+
s += " " + e.Target.String()
314314
}
315315
if e.File != nil {
316316
s += " <" + e.File.String()

interp/interp.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -910,8 +910,8 @@ func (p *interp) eval(expr Expr) (value, error) {
910910
return num(-1), nil
911911
}
912912
}
913-
if e.Var != nil {
914-
err := p.setVar(e.Var.Scope, e.Var.Index, str(line))
913+
if e.Target != nil {
914+
err := p.assign(e.Target, numStr(line))
915915
if err != nil {
916916
return null(), err
917917
}

interp/interp_test.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -596,18 +596,36 @@ BEGIN { foo(5); bar(10) }
596596
"", "", `parse error at 1:46: global var "foo" can't also be a function`, "function"},
597597
{`function f(x) { print x, x(); } BEGIN { f() }`, "", "", `parse error at 1:27: can't call local variable "x" as function`, "function"},
598598

599-
// Redirected I/O (we give explicit errors, awk and gawk don't)
600-
{`BEGIN { print >"out"; getline <"out" } # !awk !gawk`, "", "", "can't read from writer stream", ""},
601-
{`BEGIN { print |"out"; getline <"out" } # !awk !gawk`, "", "", "can't read from writer stream", ""},
602-
{`BEGIN { print >"out"; close("out"); getline <"out"; print >"out" } # !awk !gawk`, "", "", "can't write to reader stream", ""},
603-
{`BEGIN { print >"out"; close("out"); getline <"out"; print |"out" } # !awk !gawk`, "", "", "can't write to reader stream", ""},
604-
// TODO: currently we support "getline var" but not "getline lvalue"
605-
// {`BEGIN { getline a[1]; print a[1] }`, "foo", "foo\n", "", ""},
606-
// {`BEGIN { getline $1; print $1 }`, "foo", "foo\n", "", ""},
599+
// Redirected I/O
600+
{`BEGIN { getline a[1]; print a[1] }`, "foo", "foo\n", "", ""},
601+
{`BEGIN { getline $1; print $1 }`, "foo", "foo\n", "", ""},
602+
{`BEGIN { "echo foo" | getline a[1]; print a[1] }`, "", "foo\n", "", ""},
603+
{`BEGIN { "echo foo" | getline $1; print $1 }`, "", "foo\n", "", ""},
607604
{`BEGIN { print "foo" |"sort"; print "bar" |"sort" } # !fuzz`, "", "bar\nfoo\n", "", ""},
608605
{`BEGIN { print "foo" |">&2 echo error" } # !gawk !fuzz`, "", "error\n", "", ""},
609606
{`BEGIN { "cat" | getline; print } # !fuzz`, "bar", "bar\n", "", ""},
610607
{`BEGIN { print getline x < "/no/such/file" } # !fuzz`, "", "-1\n", "", ""},
608+
{`BEGIN { print getline "z"; print $0 }`, "foo", "1z\nfoo\n", "", ""},
609+
{`BEGIN { print getline x+1; print x }`, "foo", "2\nfoo\n", "", ""},
610+
{`BEGIN { print getline (x+1); print $0 }`, "foo", "11\nfoo\n", "", ""},
611+
{`BEGIN { print getline foo(); print $0 } function foo() { print "z" }`, "foo", "z\n1\nfoo\n", "", ""},
612+
// TODO: these forms don't yet work under GoAWK
613+
//{`BEGIN { print("echo foo" | getline x+1); print x }`, "", "2\nfoo\n", "", ""},
614+
//{`BEGIN { print("echo foo" | getline $0+1); print }`, "", "2\nfoo\n", "", ""},
615+
//{`BEGIN { print("echo foo" | getline ($0+1)); print }`, "", "11\nfoo\n", "", ""},
616+
//{`BEGIN { print("echo foo" | getline foo()); print } function foo() { print "z" }`, "", "z\n1\nfoo\n", "", ""},
617+
618+
// Ensure data returned by getline (in various forms) is treated as numeric string
619+
{`BEGIN { getline; print($0==0) }`, "0.0", "1\n", "", ""},
620+
{`BEGIN { getline x; print(x==0) }`, "0.0", "1\n", "", ""},
621+
{`BEGIN { "echo 0.0" | getline; print($0==0) }`, "", "1\n", "", ""},
622+
{`BEGIN { "echo 0.0" | getline x; print(x==0) }`, "", "1\n", "", ""},
623+
624+
// Redirected I/O errors (we give explicit errors, awk and gawk don't)
625+
{`BEGIN { print >"out"; getline <"out" } # !awk !gawk`, "", "", "can't read from writer stream", ""},
626+
{`BEGIN { print |"out"; getline <"out" } # !awk !gawk`, "", "", "can't read from writer stream", ""},
627+
{`BEGIN { print >"out"; close("out"); getline <"out"; print >"out" } # !awk !gawk`, "", "", "can't write to reader stream", ""},
628+
{`BEGIN { print >"out"; close("out"); getline <"out"; print |"out" } # !awk !gawk`, "", "", "can't write to reader stream", ""},
611629

612630
// Redirecting to or from a filename of "-" means write to stdout or read from stdin
613631
{`BEGIN { print getline x < "-"; print x }`, "a\nb\n", "1\na\n", "", ""},

lexer/lexer.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,3 +453,9 @@ func (l *Lexer) choice(ch byte, one, two Token) Token {
453453
}
454454
return one
455455
}
456+
457+
// PeekByte returns the next unscanned byte; used when parsing
458+
// "getline lvalue" expressions. Returns 0 at end of input.
459+
func (l *Lexer) PeekByte() byte {
460+
return l.ch
461+
}

lexer/lexer_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,34 @@ func TestHadSpace(t *testing.T) {
151151
}
152152
}
153153

154+
func TestPeekByte(t *testing.T) {
155+
l := NewLexer([]byte("foo()"))
156+
b := l.PeekByte()
157+
if b != 'f' {
158+
t.Errorf("expected 'f', got %q", b)
159+
}
160+
_, tok, _ := l.Scan()
161+
if tok != NAME {
162+
t.Errorf("expected name, got %s", tok)
163+
}
164+
b = l.PeekByte()
165+
if b != '(' {
166+
t.Errorf("expected '(', got %q", b)
167+
}
168+
_, tok, _ = l.Scan()
169+
if tok != LPAREN {
170+
t.Errorf("expected (, got %s", tok)
171+
}
172+
_, tok, _ = l.Scan()
173+
if tok != RPAREN {
174+
t.Errorf("expected ), got %s", tok)
175+
}
176+
b = l.PeekByte()
177+
if b != 0 {
178+
t.Errorf("expected 0, got %q", b)
179+
}
180+
}
181+
154182
func TestKeywordToken(t *testing.T) {
155183
tests := []struct {
156184
name string

0 commit comments

Comments
 (0)