From 2ebf74dd059ed777c7bfb2df3654735912d498e0 Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Sat, 24 Oct 2026 02:29:51 +0530 Subject: [PATCH 1/2] parser: read length without parentheses as length($0) --- lexer/src/lib.rs | 2 +- parser/src/pratt.rs | 13 ++++++++++--- tests/by-util/test_awk.rs | 9 +++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lexer/src/lib.rs b/lexer/src/lib.rs index 5c82dae..1640f64 100644 --- a/lexer/src/lib.rs +++ b/lexer/src/lib.rs @@ -107,7 +107,7 @@ pub enum Token<'a> { #[token("function", accept_expression)] #[token("func", |lex| parse_non_posix_keyword(lex, Token::Function))] Function, - #[token("length", accept_expression)] + #[token("length", accept_operator)] Length, #[token("substr", accept_expression)] Substr, diff --git a/parser/src/pratt.rs b/parser/src/pratt.rs index f002f00..0aa3439 100644 --- a/parser/src/pratt.rs +++ b/parser/src/pratt.rs @@ -9,9 +9,9 @@ use lexer::{Span, Token}; use crate::{ ArrayPlace, IdentifierExt, Lexer, Parser, Result, ast::{ - ArrayOperator, Atom, BinaryOperator, BinaryPlaceOperator, BindingPower, Expr, ExprNode, - Getline, Place, Redirection, Ternary, UnaryOperator, UnaryPlaceOperator, Variable, - WriteKind, + ArrayOperator, Atom, BinaryOperator, BinaryPlaceOperator, BindingPower, BuiltinFunction, + Expr, ExprNode, Getline, Place, Redirection, Ternary, UnaryOperator, UnaryPlaceOperator, + Variable, WriteKind, }, diagnostics::ParsingError, lex::{SpanExt, TokenExt}, @@ -358,6 +358,13 @@ impl<'a, 'b> Pratt<'a, 'b> { lex.span().since(anchor), )) } + } else if next == Token::Length && !lex.peek_is(&Token::OpenParent) { + // POSIX: `length` without an argument list is `length($0)`. + Ok(Expr::node( + ExprNode::BuiltinCall(BuiltinFunction::Length, Vec::new_in(self.parser.arena)), + self.parser, + lex.span().since(anchor), + )) } else if let Some(builtin) = next.maps_to_builtin() { self.parser .parse_function_call::(lex, |args| ExprNode::BuiltinCall(builtin, args)) diff --git a/tests/by-util/test_awk.rs b/tests/by-util/test_awk.rs index 3003aab..47f185f 100644 --- a/tests/by-util/test_awk.rs +++ b/tests/by-util/test_awk.rs @@ -982,3 +982,12 @@ fn nr_counts_across_files() { .succeeds() .stdout_is("1 1\n2 2\n3 1\n"); } + +#[test] +fn length_without_parentheses_is_length_of_record() { + ucmd() + .arg("length > 2 { print length, length / 2 } BEGIN { print length }") + .pipe_in("ab\nabcd\nxyz\n") + .succeeds() + .stdout_only("0\n4 2\n3 1.5\n"); +} From 4c3e2412d00d687ffb954fa9a2707d1de6d304db Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Sun, 25 Oct 2026 00:15:57 +0530 Subject: [PATCH 2/2] tests: cover length(/re/) and length $1 with the bare length change --- tests/by-util/test_awk.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/by-util/test_awk.rs b/tests/by-util/test_awk.rs index 47f185f..1c16d6b 100644 --- a/tests/by-util/test_awk.rs +++ b/tests/by-util/test_awk.rs @@ -990,4 +990,9 @@ fn length_without_parentheses_is_length_of_record() { .pipe_in("ab\nabcd\nxyz\n") .succeeds() .stdout_only("0\n4 2\n3 1.5\n"); + ucmd() + .arg("{ print length(/b/), length $1 }") + .pipe_in("abc def\nxyz\n") + .succeeds() + .stdout_only("1 7abc\n1 3xyz\n"); }