parser: redirection support

This commit is contained in:
Guillem L. Jara
2026-05-06 19:41:51 +02:00
parent 7b3ea1b91c
commit 886d78366a
5 changed files with 148 additions and 66 deletions
+18 -6
View File
@@ -157,11 +157,11 @@ pub enum Place<'a> {
}
/// GNU docs: https://www.gnu.org/software/gawk/manual/html_node/Redirection.html
#[derive(Debug, Clone)]
pub enum Redirection<'a> {
Write(WriteKind, Slice<'a>),
WriteFile(&'a str),
AppendFile(&'a str),
pub enum Redirection {
WriteFile,
AppendFile,
PipeIn,
CoprocessIn,
}
#[derive(Debug, Clone, Copy)]
@@ -227,7 +227,7 @@ pub enum SimpleStatement<'a> {
Command {
name: Command,
args: Vec<'a, Expr<'a>>,
redirection: Option<Redirection<'a>>,
redirection: Option<(Redirection, Expr<'a>)>,
},
Delete(Variable<'a>, Option<Expr<'a>>),
}
@@ -422,6 +422,18 @@ impl<'a> Place<'a> {
}
}
impl Redirection {
pub fn parse(value: &Token) -> Option<Self> {
match value {
Token::GreaterThan => Some(Self::WriteFile),
Token::AppendPipe => Some(Self::AppendFile),
Token::Pipe => Some(Self::PipeIn),
Token::DoublePipe => Some(Self::CoprocessIn),
_ => None,
}
}
}
impl WriteKind {
pub fn parse(value: &Token) -> Option<Self> {
match value {
+25 -8
View File
@@ -4,8 +4,8 @@ use crate::{
Ast, Function,
ast::{
Atom, BinaryOperator, BinaryPlaceOperator, BindingPower, Body, Command, Expr, ExprNode,
Getline, Place, Rule, RulePattern, SimpleStatement, Statement, Ternary, UnaryOperator,
UnaryPlaceOperator, Variable,
Getline, Place, Redirection, Rule, RulePattern, SimpleStatement, Statement, Ternary,
UnaryOperator, UnaryPlaceOperator, Variable,
},
};
@@ -184,14 +184,20 @@ impl Display for SimpleStatement<'_> {
SimpleStatement::Command {
name,
args,
redirection,
redirection: Some((rx, expr)),
} => {
write!(f, "{name}(")?;
write_args(f, args, indent)?;
write!(f, "){rx}{expr}")?;
Ok(())
}
SimpleStatement::Command {
name,
args,
redirection: None,
} => {
write!(f, "{name} ")?;
write_args(f, args, indent)?;
if let Some(_redir) = redirection {
write!(f, "TODO")?;
}
Ok(())
write_args(f, args, indent)
}
SimpleStatement::Delete(array, Some(index)) => write!(f, "delete {array}[{index}]"),
SimpleStatement::Delete(array, None) => write!(f, "delete {array}"),
@@ -397,6 +403,17 @@ impl Display for Command {
}
}
impl Display for Redirection {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::WriteFile => write!(f, " > "),
Self::AppendFile => write!(f, " >> "),
Self::PipeIn => write!(f, " | "),
Self::CoprocessIn => write!(f, " |& "),
}
}
}
fn write_args(f: &mut Formatter<'_>, args: &[impl Display], indent: u8) -> Result {
let ew = encode(indent, 0);
for (i, arg) in args.iter().enumerate() {
+48 -30
View File
@@ -25,7 +25,7 @@ pub use crate::lex::Lexer;
use crate::{
ast::{
Atom, BinaryPlaceOperator, Body, Command, Expr, ExprNode, Function, Identifier, Pattern,
Rule, RulePattern, SimpleStatement, SpecialPattern, Statement, Variable,
Redirection, Rule, RulePattern, SimpleStatement, SpecialPattern, Statement, Variable,
},
diagnostics::{ParsingError, report_error},
lex::TokenExt,
@@ -467,41 +467,31 @@ impl<'a> Parser<'a> {
}
#[tracing::instrument]
fn parse_command(
&mut self,
lex: &mut Lexer<'a>,
command: Command,
) -> Result<SimpleStatement<'a>> {
fn parse_command(&mut self, lex: &mut Lexer<'a>, name: Command) -> Result<SimpleStatement<'a>> {
let parent = lex.consume(&Token::OpenParent);
let command = SimpleStatement::Command {
name: command,
args: self.parse_arguments(lex, |t| {
if parent {
t == &Token::ClosedParent
} else {
t.is_stmnt_end() || t == &Token::ClosedBrace
}
})?,
redirection: None,
};
if parent {
let args = if parent {
let expr = self.parse_function_args(lex)?;
lex.expect(
&Token::ClosedParent,
ParsingError::UnclosedParenthesisInStatement,
)?;
}
Ok(command)
expr
} else {
self.parse_command_args(lex)?
};
let redirection = self.parse_command_redirection(lex)?;
Ok(SimpleStatement::Command {
name,
args,
redirection,
})
}
/// Parses arguments to command or function calls; consumes to the end of
/// the argument list or short-circuits with `delimiter` if empty.
fn parse_arguments(
&mut self,
lex: &mut Lexer<'a>,
delimiter: impl Fn(&Token<'a>) -> bool,
) -> Result<Vec<'a, Expr<'a>>> {
fn parse_function_args(&mut self, lex: &mut Lexer<'a>) -> Result<Vec<'a, Expr<'a>>> {
let mut arguments = Vec::new_in(self.arena);
if lex.peek_with(&delimiter) {
if lex.peek_is(&Token::ClosedParent) {
return Ok(arguments);
}
@@ -512,6 +502,37 @@ impl<'a> Parser<'a> {
Ok(arguments)
}
fn parse_command_args(&mut self, lex: &mut Lexer<'a>) -> Result<Vec<'a, Expr<'a>>> {
let mut arguments = Vec::new_in(self.arena);
let mut pratt = Pratt::new(self);
if !lex.peek_with(Token::is_expr_start) {
return Ok(arguments);
}
arguments.push(pratt.parse_command_argument(lex)?);
while lex.consume(&Token::Comma) {
arguments.push(pratt.parse_command_argument(lex)?);
}
Ok(arguments)
}
fn parse_command_redirection(
&mut self,
lex: &mut Lexer<'a>,
) -> Result<Option<(Redirection, Expr<'a>)>> {
if let Some(Ok(token)) = lex.peek()
&& let Some(redirection) = Redirection::parse(token)
{
lex.next();
Ok(Some((
redirection,
Pratt::new(self).parse_redirection(lex)?,
)))
} else {
Ok(None)
}
}
fn parse_delete(&mut self, lex: &mut Lexer<'a>) -> Result<SimpleStatement<'a>> {
let next = lex.expect_next()?;
let Some(var) = self.get_place(lex, next) else {
@@ -602,10 +623,7 @@ impl<'a> Parser<'a> {
if lex.span().start != span.end {
return Err(ParsingError::FunctionCallSeparatedIdent(span));
}
let expr = ExprNode::FunctionCall(
name,
self.parse_arguments(lex, |t| t == &Token::ClosedParent)?,
);
let expr = ExprNode::FunctionCall(name, self.parse_function_args(lex)?);
lex.expect(&Token::ClosedParent, |s| {
ParsingError::FunctionCallMissingParenthesis(s, name.to_string())
})?;
+26 -9
View File
@@ -3,8 +3,8 @@ use lexer::Token;
use crate::{
IdentifierExt, Lexer, Parser, Result,
ast::{
BinaryOperator, BinaryPlaceOperator, BindingPower, Expr, ExprNode, Getline, Place, Ternary,
UnaryOperator, UnaryPlaceOperator, Variable, WriteKind,
BinaryOperator, BinaryPlaceOperator, BindingPower, Expr, ExprNode, Getline, Place,
Redirection, Ternary, UnaryOperator, UnaryPlaceOperator, Variable, WriteKind,
},
diagnostics::ParsingError,
lex::TokenExt,
@@ -23,6 +23,11 @@ impl<'a, 'b> Pratt<'a, 'b> {
self.parse_expression(lex, 0)
}
pub fn parse_command_argument(&mut self, lex: &mut Lexer<'a>) -> Result<Expr<'a>> {
let lhs = self.parse_lhs(lex)?;
self.fold_rhs(lex, lhs, 0, |t| Redirection::parse(t).is_some())
}
fn parse_lhs(&mut self, lex: &mut Lexer<'a>) -> Result<Expr<'a>> {
if lex.consume(&Token::OpenParent) {
self.parse_parenthesized(lex)
@@ -37,12 +42,21 @@ impl<'a, 'b> Pratt<'a, 'b> {
fn parse_expression(&mut self, lex: &mut Lexer<'a>, min_bp: u8) -> Result<Expr<'a>> {
let lhs = self.parse_lhs(lex)?;
self.fold_rhs(lex, lhs, min_bp)
self.fold_rhs(lex, lhs, min_bp, |_| false)
}
fn fold_rhs(&mut self, lex: &mut Lexer<'a>, mut lhs: Expr<'a>, min_bp: u8) -> Result<Expr<'a>> {
fn fold_rhs(
&mut self,
lex: &mut Lexer<'a>,
mut lhs: Expr<'a>,
min_bp: u8,
delimiter: impl Fn(&Token<'a>) -> bool,
) -> Result<Expr<'a>> {
while let Some((next, span)) = lex.peek_with_span() {
let next = next?;
if delimiter(next) {
break;
}
lhs = if let Ok(op) = UnaryPlaceOperator::parse_suffix(next, &span) {
if op.binding_power() < min_bp {
break;
@@ -81,7 +95,7 @@ impl<'a, 'b> Pratt<'a, 'b> {
}
fn parse_parenthesized(&mut self, lex: &mut Lexer<'a>) -> Result<Expr<'a>> {
let inner = self.parse_expression(lex, 0);
let inner = self.parse(lex);
lex.expect(
&Token::ClosedParent,
ParsingError::UnclosedParenthesisExpression,
@@ -110,7 +124,7 @@ impl<'a, 'b> Pratt<'a, 'b> {
// redirection reading from file.
let place = if lex.peek_with(Token::is_place) {
Some(Place::promote_from(
self.parse_expression(lex, BinaryOperator::Concat.binding_power().1)?,
self.parse_redirection(lex)?,
lex.span(),
))
} else {
@@ -184,8 +198,7 @@ impl<'a, 'b> Pratt<'a, 'b> {
BinaryOperator::Concat.expr(
rhs,
Expr::node(
BinaryOperator::Concat
.expr(Expr::leaf(Variable::Subsep), self.parse_expression(lex, 0)?),
BinaryOperator::Concat.expr(Expr::leaf(Variable::Subsep), self.parse(lex)?),
self.parser.arena,
),
),
@@ -224,7 +237,7 @@ impl<'a, 'b> Pratt<'a, 'b> {
let pipe = |place| Expr::node(op.expr_getline(place, lhs), self.parser.arena);
if lex.peek_with(Token::is_place) {
let expr = self.parse_expression(lex, BinaryOperator::Concat.binding_power().1)?;
let expr = self.parse_redirection(lex)?;
match Place::promote_from(expr, lex.span()) {
Ok(place) => Ok(pipe(Some(place))),
Err((expr, _)) => Ok(Expr::node(
@@ -236,4 +249,8 @@ impl<'a, 'b> Pratt<'a, 'b> {
Ok(pipe(None))
}
}
pub fn parse_redirection(&mut self, lex: &mut Lexer<'a>) -> Result<Expr<'a>> {
self.parse_expression(lex, BinaryOperator::Concat.binding_power().1 - 1)
}
}
+31 -13
View File
@@ -5,7 +5,9 @@
use std::fmt::{Debug, Display, Formatter, Result};
use crate::ast::{Atom, Body, Identifier, Place, SimpleStatement, Statement, Variable};
use crate::ast::{
Atom, Body, Identifier, Place, Redirection, SimpleStatement, Statement, Variable,
};
const PRETTY_PRINT_INDENT: usize = 2;
@@ -156,22 +158,27 @@ impl Debug for SimpleStatement<'_> {
Self::Command {
name,
args,
redirection,
redirection: Some((rx, expr)),
} => {
if let Some(rx) = redirection {
if alt {
write!(
f,
"(redir {rx:?}\n{pad}({name:?}{:#ni$?}))",
ListLispFmt(args),
)
} else {
write!(f, "(redir {rx:?} ({name:?}{:?}))", ListLispFmt(args))
}
if alt {
write!(
f,
"({name:?}{:#width$?}\n{pad}({rx:?} {expr:?}))",
ListLispFmt(args),
width = ni - PRETTY_PRINT_INDENT
)
} else {
write!(f, "({name:?}{:?})", ListLispFmt(args))
write!(f, "({name:?}{:?} ({rx:?} {expr:?}))", ListLispFmt(args))
}
}
Self::Command {
name,
args,
redirection: None,
} => {
write!(f, "({name:?}{:?})", ListLispFmt(args))
}
Self::Delete(array, Some(index)) => {
write!(f, "(delete (index {array:?} {index:?}))")
}
@@ -287,3 +294,14 @@ impl Debug for Variable<'_> {
}
}
}
impl Debug for Redirection {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::WriteFile => write!(f, ">"),
Self::AppendFile => write!(f, ">>"),
Self::PipeIn => write!(f, "|"),
Self::CoprocessIn => write!(f, "|&"),
}
}
}