2020-01-18 11:38:21 +03:00
/*
2020-06-17 16:35:06 +03:00
* Copyright ( c ) 2020 , the SerenityOS developers .
2020-01-18 11:38:21 +03:00
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* 1. Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
*
* 2. Redistributions in binary form must reproduce the above copyright notice ,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY ,
* OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
2019-05-07 02:12:08 +03:00
# include "Parser.h"
2021-01-11 12:34:59 +03:00
# include "Shell.h"
# include <AK/AllOf.h>
2021-03-12 19:29:37 +03:00
# include <AK/ScopedValueRollback.h>
2020-10-24 17:43:02 +03:00
# include <AK/TemporaryChange.h>
2020-04-30 03:56:16 +03:00
# include <ctype.h>
2019-05-07 02:12:08 +03:00
# include <stdio.h>
# include <unistd.h>
2020-10-01 17:43:01 +03:00
namespace Shell {
2020-09-28 13:57:20 +03:00
Parser : : SavedOffset Parser : : save_offset ( ) const
{
return { m_offset , m_line } ;
}
2020-06-17 16:35:06 +03:00
char Parser : : peek ( )
2019-05-07 02:12:08 +03:00
{
2020-11-29 15:48:13 +03:00
if ( at_end ( ) )
2020-06-17 16:35:06 +03:00
return 0 ;
2021-02-23 22:42:32 +03:00
VERIFY ( m_offset < m_input . length ( ) ) ;
2020-09-16 03:41:09 +03:00
auto ch = m_input [ m_offset ] ;
if ( ch = = ' \\ ' & & m_input . length ( ) > m_offset + 1 & & m_input [ m_offset + 1 ] = = ' \n ' ) {
m_offset + = 2 ;
2020-09-28 13:57:20 +03:00
+ + m_line . line_number ;
m_line . line_column = 0 ;
2020-09-16 03:41:09 +03:00
return peek ( ) ;
}
return ch ;
2020-06-17 16:35:06 +03:00
}
char Parser : : consume ( )
{
2020-11-29 15:48:13 +03:00
if ( at_end ( ) )
return 0 ;
2020-06-17 16:35:06 +03:00
auto ch = peek ( ) ;
+ + m_offset ;
2020-09-16 03:41:09 +03:00
2020-09-28 13:57:20 +03:00
if ( ch = = ' \n ' ) {
+ + m_line . line_number ;
m_line . line_column = 0 ;
} else {
+ + m_line . line_column ;
}
2020-06-17 16:35:06 +03:00
2020-09-28 13:57:20 +03:00
return ch ;
2020-06-17 16:35:06 +03:00
}
bool Parser : : expect ( char ch )
{
return expect ( StringView { & ch , 1 } ) ;
}
bool Parser : : expect ( const StringView & expected )
{
2020-09-14 18:01:47 +03:00
auto offset_at_start = m_offset ;
2020-09-28 13:57:20 +03:00
auto line_at_start = line ( ) ;
2020-09-14 18:01:47 +03:00
2020-06-17 16:35:06 +03:00
if ( expected . length ( ) + m_offset > m_input . length ( ) )
return false ;
2021-03-05 16:03:23 +03:00
for ( auto & c : expected ) {
if ( peek ( ) ! = c ) {
2020-09-28 13:57:20 +03:00
restore_to ( offset_at_start , line_at_start ) ;
2020-06-17 16:35:06 +03:00
return false ;
2020-09-14 18:01:47 +03:00
}
2020-06-17 16:35:06 +03:00
consume ( ) ;
2019-05-07 02:12:08 +03:00
}
2020-06-17 16:35:06 +03:00
return true ;
}
template < typename A , typename . . . Args >
2020-08-04 19:16:37 +03:00
NonnullRefPtr < A > Parser : : create ( Args . . . args )
2019-05-07 02:12:08 +03:00
{
2020-09-28 13:57:20 +03:00
return adopt ( * new A ( AST : : Position { m_rule_start_offsets . last ( ) , m_offset , m_rule_start_lines . last ( ) , line ( ) } , args . . . ) ) ;
2019-05-07 02:12:08 +03:00
}
2020-06-17 16:35:06 +03:00
[[nodiscard]] OwnPtr < Parser : : ScopedOffset > Parser : : push_start ( )
2019-08-30 07:54:05 +03:00
{
2020-09-28 13:57:20 +03:00
return make < ScopedOffset > ( m_rule_start_offsets , m_rule_start_lines , m_offset , m_line . line_number , m_line . line_column ) ;
2019-08-30 07:54:05 +03:00
}
2021-01-16 22:50:52 +03:00
Parser : : Offset Parser : : current_position ( )
{
return Offset { m_offset , { m_line . line_number , m_line . line_column } } ;
}
2020-06-17 16:35:06 +03:00
static constexpr bool is_whitespace ( char c )
2019-05-07 02:12:08 +03:00
{
2020-06-17 16:35:06 +03:00
return c = = ' ' | | c = = ' \t ' ;
2019-05-07 02:12:08 +03:00
}
2020-06-17 16:35:06 +03:00
static constexpr bool is_digit ( char c )
2019-05-07 02:12:08 +03:00
{
2020-06-17 16:35:06 +03:00
return c < = ' 9 ' & & c > = ' 0 ' ;
2019-05-07 02:12:08 +03:00
}
2020-06-17 16:35:06 +03:00
static constexpr auto is_not ( char c )
2020-04-30 03:56:16 +03:00
{
2020-06-17 16:35:06 +03:00
return [ c ] ( char ch ) { return ch ! = c ; } ;
2020-04-30 03:56:16 +03:00
}
2020-06-17 16:35:06 +03:00
static inline char to_byte ( char a , char b )
2019-05-07 02:12:08 +03:00
{
2020-06-17 16:35:06 +03:00
char buf [ 3 ] { a , b , 0 } ;
return strtol ( buf , nullptr , 16 ) ;
}
2020-05-24 21:30:46 +03:00
2020-06-17 16:35:06 +03:00
RefPtr < AST : : Node > Parser : : parse ( )
{
m_offset = 0 ;
2020-09-28 13:57:20 +03:00
m_line = { 0 , 0 } ;
2020-05-24 21:30:46 +03:00
2020-06-23 17:40:41 +03:00
auto toplevel = parse_toplevel ( ) ;
if ( m_offset < m_input . length ( ) ) {
// Parsing stopped midway, this is a syntax error.
auto error_start = push_start ( ) ;
2020-11-29 15:48:13 +03:00
while ( ! at_end ( ) )
consume ( ) ;
2020-07-07 15:46:01 +03:00
auto syntax_error_node = create < AST : : SyntaxError > ( " Unexpected tokens past the end " ) ;
2020-09-16 03:37:14 +03:00
if ( ! toplevel )
toplevel = move ( syntax_error_node ) ;
2020-12-01 12:25:14 +03:00
else if ( ! toplevel - > is_syntax_error ( ) )
2020-09-16 03:37:14 +03:00
toplevel - > set_is_syntax_error ( * syntax_error_node ) ;
2020-06-23 17:40:41 +03:00
}
return toplevel ;
2020-06-17 16:35:06 +03:00
}
2021-01-11 12:34:59 +03:00
RefPtr < AST : : Node > Parser : : parse_as_single_expression ( )
{
auto input = Shell : : escape_token_for_double_quotes ( m_input ) ;
Parser parser { input } ;
return parser . parse_expression ( ) ;
}
NonnullRefPtrVector < AST : : Node > Parser : : parse_as_multiple_expressions ( )
{
NonnullRefPtrVector < AST : : Node > nodes ;
for ( ; ; ) {
consume_while ( is_whitespace ) ;
auto node = parse_expression ( ) ;
if ( ! node )
node = parse_redirection ( ) ;
if ( ! node )
return nodes ;
nodes . append ( node . release_nonnull ( ) ) ;
}
return nodes ;
}
2020-06-17 16:35:06 +03:00
RefPtr < AST : : Node > Parser : : parse_toplevel ( )
{
auto rule_start = push_start ( ) ;
2021-01-16 22:50:52 +03:00
SequenceParseResult result ;
NonnullRefPtrVector < AST : : Node > sequence ;
Vector < AST : : Position > positions ;
do {
result = parse_sequence ( ) ;
if ( result . entries . is_empty ( ) )
break ;
sequence . append ( move ( result . entries ) ) ;
positions . append ( move ( result . separator_positions ) ) ;
} while ( result . decision = = ShouldReadMoreSequences : : Yes ) ;
if ( sequence . is_empty ( ) )
return nullptr ;
2020-06-17 16:35:06 +03:00
2021-01-16 22:50:52 +03:00
return create < AST : : Execute > (
create < AST : : Sequence > ( move ( sequence ) , move ( positions ) ) ) ;
2020-06-17 16:35:06 +03:00
}
2021-01-16 22:50:52 +03:00
Parser : : SequenceParseResult Parser : : parse_sequence ( )
2020-06-17 16:35:06 +03:00
{
2020-07-27 04:28:41 +03:00
consume_while ( is_any_of ( " \t \n ; " ) ) ; // ignore whitespaces or terminators without effect.
2020-07-12 00:12:46 +03:00
2021-01-16 22:50:52 +03:00
NonnullRefPtrVector < AST : : Node > left ;
2020-06-17 16:35:06 +03:00
auto rule_start = push_start ( ) ;
2021-01-16 22:50:52 +03:00
{
auto var_decls = parse_variable_decls ( ) ;
if ( var_decls )
left . append ( var_decls . release_nonnull ( ) ) ;
}
2020-05-24 21:30:46 +03:00
2020-09-28 13:57:20 +03:00
auto pos_before_seps = save_offset ( ) ;
2020-06-28 17:13:37 +03:00
switch ( peek ( ) ) {
2020-07-12 00:12:46 +03:00
case ' } ' :
2021-01-16 22:50:52 +03:00
return { move ( left ) , { } , ShouldReadMoreSequences : : No } ;
2020-06-28 17:13:37 +03:00
case ' ; ' :
2020-07-12 00:12:46 +03:00
case ' \n ' : {
2021-01-16 22:50:52 +03:00
if ( left . is_empty ( ) )
2020-07-12 00:12:46 +03:00
break ;
2020-06-28 17:13:37 +03:00
consume_while ( is_any_of ( " \n ; " ) ) ;
2020-09-28 13:57:20 +03:00
auto pos_after_seps = save_offset ( ) ;
2021-01-16 22:50:52 +03:00
AST : : Position separator_position { pos_before_seps . offset , pos_after_seps . offset , pos_before_seps . line , pos_after_seps . line } ;
2020-09-28 13:57:20 +03:00
2021-01-16 22:50:52 +03:00
return { move ( left ) , { move ( separator_position ) } , ShouldReadMoreSequences : : Yes } ;
2020-07-12 00:12:46 +03:00
}
2020-06-28 17:13:37 +03:00
default :
break ;
}
2021-01-16 22:50:52 +03:00
auto first_entry = parse_function_decl ( ) ;
2020-09-13 14:24:33 +03:00
2021-01-16 22:50:52 +03:00
Vector < AST : : Position > separator_positions ;
2020-07-12 00:12:46 +03:00
2021-01-16 22:50:52 +03:00
if ( ! first_entry )
first_entry = parse_or_logical_sequence ( ) ;
2020-06-17 16:35:06 +03:00
2021-01-16 22:50:52 +03:00
if ( ! first_entry )
return { move ( left ) , { } , ShouldReadMoreSequences : : No } ;
left . append ( first_entry . release_nonnull ( ) ) ;
separator_positions . empend ( pos_before_seps . offset , pos_before_seps . offset , pos_before_seps . line , pos_before_seps . line ) ;
2020-06-17 16:35:06 +03:00
consume_while ( is_whitespace ) ;
2020-09-28 13:57:20 +03:00
pos_before_seps = save_offset ( ) ;
2020-06-17 16:35:06 +03:00
switch ( peek ( ) ) {
case ' ; ' :
2020-09-28 13:57:20 +03:00
case ' \n ' : {
2020-06-28 17:13:37 +03:00
consume_while ( is_any_of ( " \n ; " ) ) ;
2020-09-28 13:57:20 +03:00
auto pos_after_seps = save_offset ( ) ;
2021-01-16 22:50:52 +03:00
separator_positions . empend ( pos_before_seps . offset , pos_after_seps . offset , pos_before_seps . line , pos_after_seps . line ) ;
return { move ( left ) , move ( separator_positions ) , ShouldReadMoreSequences : : Yes } ;
2020-09-28 13:57:20 +03:00
}
2020-06-17 16:35:06 +03:00
case ' & ' : {
consume ( ) ;
2020-09-28 13:57:20 +03:00
auto pos_after_seps = save_offset ( ) ;
2021-01-16 22:50:52 +03:00
auto bg = create < AST : : Background > ( left . take_last ( ) ) ; // Execute Background
left . append ( move ( bg ) ) ;
separator_positions . empend ( pos_before_seps . offset , pos_after_seps . offset , pos_before_seps . line , pos_after_seps . line ) ;
return { move ( left ) , move ( separator_positions ) , ShouldReadMoreSequences : : Yes } ;
2020-06-17 16:35:06 +03:00
}
default :
2021-01-16 22:50:52 +03:00
return { move ( left ) , move ( separator_positions ) , ShouldReadMoreSequences : : No } ;
2020-06-17 16:35:06 +03:00
}
}
RefPtr < AST : : Node > Parser : : parse_variable_decls ( )
{
auto rule_start = push_start ( ) ;
consume_while ( is_whitespace ) ;
2020-09-28 13:57:20 +03:00
auto pos_before_name = save_offset ( ) ;
2020-06-17 16:35:06 +03:00
auto var_name = consume_while ( is_word_character ) ;
if ( var_name . is_empty ( ) )
return nullptr ;
if ( ! expect ( ' = ' ) ) {
2020-09-28 13:57:20 +03:00
restore_to ( pos_before_name . offset , pos_before_name . line ) ;
2020-06-17 16:35:06 +03:00
return nullptr ;
}
auto name_expr = create < AST : : BarewordLiteral > ( move ( var_name ) ) ;
2020-06-22 14:07:20 +03:00
auto start = push_start ( ) ;
2020-06-17 16:35:06 +03:00
auto expression = parse_expression ( ) ;
2020-06-22 14:07:20 +03:00
if ( ! expression | | expression - > is_syntax_error ( ) ) {
2020-09-28 13:57:20 +03:00
restore_to ( * start ) ;
2020-06-22 14:07:20 +03:00
if ( peek ( ) = = ' ( ' ) {
consume ( ) ;
auto command = parse_pipe_sequence ( ) ;
if ( ! command )
2020-09-28 13:57:20 +03:00
restore_to ( * start ) ;
2020-06-23 17:40:41 +03:00
else if ( ! expect ( ' ) ' ) )
2020-12-01 12:25:14 +03:00
command - > set_is_syntax_error ( * create < AST : : SyntaxError > ( " Expected a terminating close paren " , true ) ) ;
2020-06-22 14:07:20 +03:00
expression = command ;
}
}
2020-06-17 16:35:06 +03:00
if ( ! expression ) {
if ( is_whitespace ( peek ( ) ) ) {
auto string_start = push_start ( ) ;
expression = create < AST : : StringLiteral > ( " " ) ;
} else {
2020-09-28 13:57:20 +03:00
restore_to ( pos_before_name . offset , pos_before_name . line ) ;
2020-06-17 16:35:06 +03:00
return nullptr ;
}
}
Vector < AST : : VariableDeclarations : : Variable > variables ;
2020-08-07 10:41:04 +03:00
variables . append ( { move ( name_expr ) , expression . release_nonnull ( ) } ) ;
2020-06-17 16:35:06 +03:00
if ( consume_while ( is_whitespace ) . is_empty ( ) )
return create < AST : : VariableDeclarations > ( move ( variables ) ) ;
auto rest = parse_variable_decls ( ) ;
if ( ! rest )
return create < AST : : VariableDeclarations > ( move ( variables ) ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( rest - > is_variable_decls ( ) ) ;
2020-06-17 16:35:06 +03:00
auto * rest_decl = static_cast < AST : : VariableDeclarations * > ( rest . ptr ( ) ) ;
variables . append ( rest_decl - > variables ( ) ) ;
return create < AST : : VariableDeclarations > ( move ( variables ) ) ;
}
2020-09-13 14:24:33 +03:00
RefPtr < AST : : Node > Parser : : parse_function_decl ( )
{
auto rule_start = push_start ( ) ;
auto restore = [ & ] {
2020-09-28 13:57:20 +03:00
restore_to ( * rule_start ) ;
2020-09-13 14:24:33 +03:00
return nullptr ;
} ;
consume_while ( is_whitespace ) ;
2020-09-28 13:57:20 +03:00
auto pos_before_name = save_offset ( ) ;
2020-09-13 14:24:33 +03:00
auto function_name = consume_while ( is_word_character ) ;
2020-09-28 13:57:20 +03:00
auto pos_after_name = save_offset ( ) ;
2020-09-13 14:24:33 +03:00
if ( function_name . is_empty ( ) )
return restore ( ) ;
if ( ! expect ( ' ( ' ) )
return restore ( ) ;
2021-03-05 16:03:23 +03:00
Vector < AST : : NameWithPosition > arguments ;
2020-09-13 14:24:33 +03:00
for ( ; ; ) {
consume_while ( is_whitespace ) ;
if ( expect ( ' ) ' ) )
break ;
auto name_offset = m_offset ;
2020-09-28 13:57:20 +03:00
auto start_line = line ( ) ;
2020-09-13 14:24:33 +03:00
auto arg_name = consume_while ( is_word_character ) ;
if ( arg_name . is_empty ( ) ) {
// FIXME: Should this be a syntax error, or just return?
return restore ( ) ;
}
2020-09-28 13:57:20 +03:00
arguments . append ( { arg_name , { name_offset , m_offset , start_line , line ( ) } } ) ;
2020-09-13 14:24:33 +03:00
}
consume_while ( is_whitespace ) ;
{
RefPtr < AST : : Node > syntax_error ;
{
auto obrace_error_start = push_start ( ) ;
2020-12-01 12:25:14 +03:00
syntax_error = create < AST : : SyntaxError > ( " Expected an open brace '{' to start a function body " , true ) ;
2020-09-13 14:24:33 +03:00
}
if ( ! expect ( ' { ' ) ) {
return create < AST : : FunctionDeclaration > (
2021-03-05 16:03:23 +03:00
AST : : NameWithPosition {
2020-09-13 14:24:33 +03:00
move ( function_name ) ,
2020-09-28 13:57:20 +03:00
{ pos_before_name . offset , pos_after_name . offset , pos_before_name . line , pos_after_name . line } } ,
2020-09-13 14:24:33 +03:00
move ( arguments ) ,
move ( syntax_error ) ) ;
}
}
2020-12-10 17:55:13 +03:00
TemporaryChange controls { m_continuation_controls_allowed , false } ;
2020-09-13 14:24:33 +03:00
auto body = parse_toplevel ( ) ;
{
RefPtr < AST : : SyntaxError > syntax_error ;
{
auto cbrace_error_start = push_start ( ) ;
2020-12-01 12:25:14 +03:00
syntax_error = create < AST : : SyntaxError > ( " Expected a close brace '}' to end a function body " , true ) ;
2020-09-13 14:24:33 +03:00
}
if ( ! expect ( ' } ' ) ) {
if ( body )
body - > set_is_syntax_error ( * syntax_error ) ;
else
body = move ( syntax_error ) ;
return create < AST : : FunctionDeclaration > (
2021-03-05 16:03:23 +03:00
AST : : NameWithPosition {
2020-09-13 14:24:33 +03:00
move ( function_name ) ,
2020-09-28 13:57:20 +03:00
{ pos_before_name . offset , pos_after_name . offset , pos_before_name . line , pos_after_name . line } } ,
2020-09-13 14:24:33 +03:00
move ( arguments ) ,
move ( body ) ) ;
}
}
return create < AST : : FunctionDeclaration > (
2021-03-05 16:03:23 +03:00
AST : : NameWithPosition {
2020-09-13 14:24:33 +03:00
move ( function_name ) ,
2020-09-28 13:57:20 +03:00
{ pos_before_name . offset , pos_after_name . offset , pos_before_name . line , pos_after_name . line } } ,
2020-09-13 14:24:33 +03:00
move ( arguments ) ,
move ( body ) ) ;
}
2020-07-12 00:12:46 +03:00
RefPtr < AST : : Node > Parser : : parse_or_logical_sequence ( )
{
consume_while ( is_whitespace ) ;
auto rule_start = push_start ( ) ;
auto and_sequence = parse_and_logical_sequence ( ) ;
if ( ! and_sequence )
return nullptr ;
consume_while ( is_whitespace ) ;
2020-09-28 13:57:20 +03:00
auto pos_before_or = save_offset ( ) ;
if ( ! expect ( " || " ) )
2020-07-12 00:12:46 +03:00
return and_sequence ;
2020-09-28 13:57:20 +03:00
auto pos_after_or = save_offset ( ) ;
2020-07-12 00:12:46 +03:00
auto right_and_sequence = parse_and_logical_sequence ( ) ;
if ( ! right_and_sequence )
2020-12-01 12:25:14 +03:00
right_and_sequence = create < AST : : SyntaxError > ( " Expected an expression after '||' " , true ) ;
2020-07-12 00:12:46 +03:00
2020-09-28 13:57:20 +03:00
return create < AST : : Or > (
and_sequence . release_nonnull ( ) ,
right_and_sequence . release_nonnull ( ) ,
AST : : Position { pos_before_or . offset , pos_after_or . offset , pos_before_or . line , pos_after_or . line } ) ;
2020-07-12 00:12:46 +03:00
}
RefPtr < AST : : Node > Parser : : parse_and_logical_sequence ( )
{
consume_while ( is_whitespace ) ;
auto rule_start = push_start ( ) ;
auto pipe_sequence = parse_pipe_sequence ( ) ;
if ( ! pipe_sequence )
return nullptr ;
consume_while ( is_whitespace ) ;
2020-09-28 13:57:20 +03:00
auto pos_before_and = save_offset ( ) ;
if ( ! expect ( " && " ) )
2020-07-12 00:12:46 +03:00
return pipe_sequence ;
2020-09-28 13:57:20 +03:00
auto pos_after_end = save_offset ( ) ;
2020-07-12 00:12:46 +03:00
2020-07-16 19:12:12 +03:00
auto right_and_sequence = parse_and_logical_sequence ( ) ;
if ( ! right_and_sequence )
2020-12-01 12:25:14 +03:00
right_and_sequence = create < AST : : SyntaxError > ( " Expected an expression after '&&' " , true ) ;
2020-07-12 00:12:46 +03:00
2020-09-28 13:57:20 +03:00
return create < AST : : And > (
pipe_sequence . release_nonnull ( ) ,
right_and_sequence . release_nonnull ( ) ,
AST : : Position { pos_before_and . offset , pos_after_end . offset , pos_before_and . line , pos_after_end . line } ) ;
2020-07-12 00:12:46 +03:00
}
2020-06-17 16:35:06 +03:00
RefPtr < AST : : Node > Parser : : parse_pipe_sequence ( )
{
auto rule_start = push_start ( ) ;
2020-09-07 19:19:53 +03:00
auto left = parse_control_structure ( ) ;
if ( ! left ) {
if ( auto cmd = parse_command ( ) )
left = cmd ;
else
return nullptr ;
}
2020-06-17 16:35:06 +03:00
consume_while ( is_whitespace ) ;
if ( peek ( ) ! = ' | ' )
2020-09-07 19:19:53 +03:00
return left ;
2020-06-17 16:35:06 +03:00
2020-09-28 13:57:20 +03:00
auto before_pipe = save_offset ( ) ;
2020-06-17 16:35:06 +03:00
consume ( ) ;
if ( auto pipe_seq = parse_pipe_sequence ( ) ) {
2020-09-16 03:37:14 +03:00
return create < AST : : Pipe > ( left . release_nonnull ( ) , pipe_seq . release_nonnull ( ) ) ; // Pipe
2020-06-17 16:35:06 +03:00
}
2020-09-28 13:57:20 +03:00
restore_to ( before_pipe . offset , before_pipe . line ) ;
2020-09-07 19:19:53 +03:00
return left ;
2020-06-17 16:35:06 +03:00
}
RefPtr < AST : : Node > Parser : : parse_command ( )
{
auto rule_start = push_start ( ) ;
consume_while ( is_whitespace ) ;
auto redir = parse_redirection ( ) ;
if ( ! redir ) {
auto list_expr = parse_list_expression ( ) ;
if ( ! list_expr )
return nullptr ;
2020-09-16 03:37:14 +03:00
auto cast = create < AST : : CastToCommand > ( list_expr . release_nonnull ( ) ) ; // Cast List Command
2020-06-20 16:30:45 +03:00
auto next_command = parse_command ( ) ;
2020-06-17 16:35:06 +03:00
if ( ! next_command )
return cast ;
2020-09-16 03:37:14 +03:00
return create < AST : : Join > ( move ( cast ) , next_command . release_nonnull ( ) ) ; // Join List Command
2020-06-17 16:35:06 +03:00
}
auto command = parse_command ( ) ;
if ( ! command )
return redir ;
2020-09-16 03:37:14 +03:00
return create < AST : : Join > ( redir . release_nonnull ( ) , command . release_nonnull ( ) ) ; // Join Command Command
2020-06-17 16:35:06 +03:00
}
2020-07-12 00:12:46 +03:00
RefPtr < AST : : Node > Parser : : parse_control_structure ( )
{
auto rule_start = push_start ( ) ;
consume_while ( is_whitespace ) ;
2020-12-10 17:55:13 +03:00
if ( auto control = parse_continuation_control ( ) )
return control ;
2020-07-12 00:12:46 +03:00
if ( auto for_loop = parse_for_loop ( ) )
return for_loop ;
2020-12-10 17:55:13 +03:00
if ( auto loop = parse_loop_loop ( ) )
return loop ;
2020-08-11 10:35:46 +03:00
if ( auto if_expr = parse_if_expr ( ) )
return if_expr ;
2020-09-08 14:29:07 +03:00
if ( auto subshell = parse_subshell ( ) )
return subshell ;
2020-09-14 18:02:21 +03:00
if ( auto match = parse_match_expr ( ) )
return match ;
2020-07-12 00:12:46 +03:00
return nullptr ;
}
2020-12-10 17:55:13 +03:00
RefPtr < AST : : Node > Parser : : parse_continuation_control ( )
{
if ( ! m_continuation_controls_allowed )
return nullptr ;
auto rule_start = push_start ( ) ;
if ( expect ( " break " ) ) {
{
auto break_end = push_start ( ) ;
if ( consume_while ( is_any_of ( " \t \n ; " ) ) . is_empty ( ) ) {
restore_to ( * rule_start ) ;
return nullptr ;
}
restore_to ( * break_end ) ;
}
return create < AST : : ContinuationControl > ( AST : : ContinuationControl : : Break ) ;
}
if ( expect ( " continue " ) ) {
{
auto continue_end = push_start ( ) ;
if ( consume_while ( is_any_of ( " \t \n ; " ) ) . is_empty ( ) ) {
restore_to ( * rule_start ) ;
return nullptr ;
}
restore_to ( * continue_end ) ;
}
return create < AST : : ContinuationControl > ( AST : : ContinuationControl : : Continue ) ;
}
return nullptr ;
}
2020-07-12 00:12:46 +03:00
RefPtr < AST : : Node > Parser : : parse_for_loop ( )
{
auto rule_start = push_start ( ) ;
2020-09-28 13:57:20 +03:00
if ( ! expect ( " for " ) )
2020-07-12 00:12:46 +03:00
return nullptr ;
if ( consume_while ( is_any_of ( " \t \n " ) ) . is_empty ( ) ) {
2020-09-28 13:57:20 +03:00
restore_to ( * rule_start ) ;
2020-07-12 00:12:46 +03:00
return nullptr ;
}
2021-03-05 17:55:09 +03:00
Optional < AST : : NameWithPosition > index_variable_name , variable_name ;
Optional < AST : : Position > in_start_position , index_start_position ;
auto offset_before_index = current_position ( ) ;
if ( expect ( " index " ) ) {
auto offset = current_position ( ) ;
if ( ! consume_while ( is_whitespace ) . is_empty ( ) ) {
auto offset_before_variable = current_position ( ) ;
auto variable = consume_while ( is_word_character ) ;
if ( ! variable . is_empty ( ) ) {
index_start_position = AST : : Position { offset_before_index . offset , offset . offset , offset_before_index . line , offset . line } ;
auto offset_after_variable = current_position ( ) ;
index_variable_name = AST : : NameWithPosition {
variable ,
{ offset_before_variable . offset , offset_after_variable . offset , offset_before_variable . line , offset_after_variable . line } ,
} ;
consume_while ( is_whitespace ) ;
} else {
restore_to ( offset_before_index . offset , offset_before_index . line ) ;
}
} else {
restore_to ( offset_before_index . offset , offset_before_index . line ) ;
}
}
auto variable_name_start_offset = current_position ( ) ;
auto name = consume_while ( is_word_character ) ;
auto variable_name_end_offset = current_position ( ) ;
if ( ! name . is_empty ( ) ) {
variable_name = AST : : NameWithPosition {
name ,
{ variable_name_start_offset . offset , variable_name_end_offset . offset , variable_name_start_offset . line , variable_name_end_offset . line }
} ;
2020-07-12 00:12:46 +03:00
consume_while ( is_whitespace ) ;
auto in_error_start = push_start ( ) ;
if ( ! expect ( " in " ) ) {
2020-12-01 12:25:14 +03:00
auto syntax_error = create < AST : : SyntaxError > ( " Expected 'in' after a variable name in a 'for' loop " , true ) ;
2021-03-05 17:55:09 +03:00
return create < AST : : ForLoop > ( move ( variable_name ) , move ( index_variable_name ) , move ( syntax_error ) , nullptr ) ; // ForLoop Var Iterated Block
2020-07-12 00:12:46 +03:00
}
2020-09-28 13:57:20 +03:00
in_start_position = AST : : Position { in_error_start - > offset , m_offset , in_error_start - > line , line ( ) } ;
2020-07-12 00:12:46 +03:00
}
consume_while ( is_whitespace ) ;
RefPtr < AST : : Node > iterated_expression ;
{
auto iter_error_start = push_start ( ) ;
iterated_expression = parse_expression ( ) ;
2020-12-10 17:55:13 +03:00
if ( ! iterated_expression )
iterated_expression = create < AST : : SyntaxError > ( " Expected an expression in 'for' loop " , true ) ;
2020-07-12 00:12:46 +03:00
}
consume_while ( is_any_of ( " \t \n " ) ) ;
{
auto obrace_error_start = push_start ( ) ;
if ( ! expect ( ' { ' ) ) {
2020-12-01 12:25:14 +03:00
auto syntax_error = create < AST : : SyntaxError > ( " Expected an open brace '{' to start a 'for' loop body " , true ) ;
2021-03-05 17:55:09 +03:00
return create < AST : : ForLoop > ( move ( variable_name ) , move ( index_variable_name ) , move ( iterated_expression ) , move ( syntax_error ) , move ( in_start_position ) , move ( index_start_position ) ) ; // ForLoop Var Iterated Block
2020-07-12 00:12:46 +03:00
}
}
2020-12-10 17:55:13 +03:00
TemporaryChange controls { m_continuation_controls_allowed , true } ;
2020-07-12 00:12:46 +03:00
auto body = parse_toplevel ( ) ;
{
auto cbrace_error_start = push_start ( ) ;
if ( ! expect ( ' } ' ) ) {
auto error_start = push_start ( ) ;
2020-12-01 12:25:14 +03:00
auto syntax_error = create < AST : : SyntaxError > ( " Expected a close brace '}' to end a 'for' loop body " , true ) ;
2020-07-12 00:12:46 +03:00
if ( body )
body - > set_is_syntax_error ( * syntax_error ) ;
else
body = syntax_error ;
}
}
2021-03-05 17:55:09 +03:00
return create < AST : : ForLoop > ( move ( variable_name ) , move ( index_variable_name ) , move ( iterated_expression ) , move ( body ) , move ( in_start_position ) , move ( index_start_position ) ) ; // ForLoop Var Iterated Block
2020-12-10 17:55:13 +03:00
}
RefPtr < AST : : Node > Parser : : parse_loop_loop ( )
{
auto rule_start = push_start ( ) ;
if ( ! expect ( " loop " ) )
return nullptr ;
if ( consume_while ( is_any_of ( " \t \n " ) ) . is_empty ( ) ) {
restore_to ( * rule_start ) ;
return nullptr ;
}
{
auto obrace_error_start = push_start ( ) ;
if ( ! expect ( ' { ' ) ) {
auto syntax_error = create < AST : : SyntaxError > ( " Expected an open brace '{' to start a 'loop' loop body " , true ) ;
2021-03-05 17:55:09 +03:00
return create < AST : : ForLoop > ( AST : : NameWithPosition { } , AST : : NameWithPosition { } , nullptr , move ( syntax_error ) ) ; // ForLoop null null Block
2020-12-10 17:55:13 +03:00
}
}
TemporaryChange controls { m_continuation_controls_allowed , true } ;
auto body = parse_toplevel ( ) ;
{
auto cbrace_error_start = push_start ( ) ;
if ( ! expect ( ' } ' ) ) {
auto error_start = push_start ( ) ;
auto syntax_error = create < AST : : SyntaxError > ( " Expected a close brace '}' to end a 'loop' loop body " , true ) ;
if ( body )
body - > set_is_syntax_error ( * syntax_error ) ;
else
body = syntax_error ;
}
}
2021-03-05 17:55:09 +03:00
return create < AST : : ForLoop > ( AST : : NameWithPosition { } , AST : : NameWithPosition { } , nullptr , move ( body ) ) ; // ForLoop null null Block
2020-07-12 00:12:46 +03:00
}
2020-08-11 10:35:46 +03:00
RefPtr < AST : : Node > Parser : : parse_if_expr ( )
{
auto rule_start = push_start ( ) ;
2020-09-28 13:57:20 +03:00
if ( ! expect ( " if " ) )
2020-08-11 10:35:46 +03:00
return nullptr ;
if ( consume_while ( is_any_of ( " \t \n " ) ) . is_empty ( ) ) {
2020-09-28 13:57:20 +03:00
restore_to ( * rule_start ) ;
2020-08-11 10:35:46 +03:00
return nullptr ;
}
RefPtr < AST : : Node > condition ;
{
auto cond_error_start = push_start ( ) ;
condition = parse_or_logical_sequence ( ) ;
2020-09-16 03:37:14 +03:00
if ( ! condition )
2020-12-01 12:25:14 +03:00
condition = create < AST : : SyntaxError > ( " Expected a logical sequence after 'if' " , true ) ;
2020-08-11 10:35:46 +03:00
}
auto parse_braced_toplevel = [ & ] ( ) - > RefPtr < AST : : Node > {
2020-09-16 03:37:14 +03:00
RefPtr < AST : : Node > body ;
2020-08-11 10:35:46 +03:00
{
auto obrace_error_start = push_start ( ) ;
if ( ! expect ( ' { ' ) ) {
2020-12-01 12:25:14 +03:00
body = create < AST : : SyntaxError > ( " Expected an open brace '{' to start an 'if' true branch " , true ) ;
2020-08-11 10:35:46 +03:00
}
}
2020-09-16 03:37:14 +03:00
if ( ! body )
body = parse_toplevel ( ) ;
2020-08-11 10:35:46 +03:00
{
auto cbrace_error_start = push_start ( ) ;
if ( ! expect ( ' } ' ) ) {
auto error_start = push_start ( ) ;
2020-12-01 12:25:14 +03:00
RefPtr < AST : : SyntaxError > syntax_error = create < AST : : SyntaxError > ( " Expected a close brace '}' to end an 'if' true branch " , true ) ;
2020-08-11 10:35:46 +03:00
if ( body )
body - > set_is_syntax_error ( * syntax_error ) ;
else
body = syntax_error ;
}
}
return body ;
} ;
2021-01-17 22:03:59 +03:00
consume_while ( is_any_of ( " \t \n " ) ) ;
2020-08-11 10:35:46 +03:00
auto true_branch = parse_braced_toplevel ( ) ;
2021-01-17 22:03:59 +03:00
auto end_before_else = m_offset ;
auto line_before_else = line ( ) ;
consume_while ( is_any_of ( " \t \n " ) ) ;
2020-08-11 10:35:46 +03:00
Optional < AST : : Position > else_position ;
{
auto else_start = push_start ( ) ;
if ( expect ( " else " ) )
2020-09-28 13:57:20 +03:00
else_position = AST : : Position { else_start - > offset , m_offset , else_start - > line , line ( ) } ;
2021-01-17 22:03:59 +03:00
else
restore_to ( end_before_else , line_before_else ) ;
2020-08-11 10:35:46 +03:00
}
if ( else_position . has_value ( ) ) {
2021-01-17 22:03:59 +03:00
consume_while ( is_any_of ( " \t \n " ) ) ;
2020-08-11 10:35:46 +03:00
if ( peek ( ) = = ' { ' ) {
auto false_branch = parse_braced_toplevel ( ) ;
2020-09-16 03:37:14 +03:00
return create < AST : : IfCond > ( else_position , condition . release_nonnull ( ) , move ( true_branch ) , move ( false_branch ) ) ; // If expr true_branch Else false_branch
2020-08-11 10:35:46 +03:00
}
auto else_if_branch = parse_if_expr ( ) ;
2020-09-16 03:37:14 +03:00
return create < AST : : IfCond > ( else_position , condition . release_nonnull ( ) , move ( true_branch ) , move ( else_if_branch ) ) ; // If expr true_branch Else If ...
2020-08-11 10:35:46 +03:00
}
2020-09-16 03:37:14 +03:00
return create < AST : : IfCond > ( else_position , condition . release_nonnull ( ) , move ( true_branch ) , nullptr ) ; // If expr true_branch
2020-08-11 10:35:46 +03:00
}
2020-09-08 14:29:07 +03:00
RefPtr < AST : : Node > Parser : : parse_subshell ( )
{
auto rule_start = push_start ( ) ;
if ( ! expect ( ' { ' ) )
return nullptr ;
auto body = parse_toplevel ( ) ;
{
auto cbrace_error_start = push_start ( ) ;
if ( ! expect ( ' } ' ) ) {
auto error_start = push_start ( ) ;
2020-12-01 12:25:14 +03:00
RefPtr < AST : : SyntaxError > syntax_error = create < AST : : SyntaxError > ( " Expected a close brace '}' to end a subshell " , true ) ;
2020-09-08 14:29:07 +03:00
if ( body )
body - > set_is_syntax_error ( * syntax_error ) ;
else
body = syntax_error ;
}
}
return create < AST : : Subshell > ( move ( body ) ) ;
}
2020-09-14 18:02:21 +03:00
RefPtr < AST : : Node > Parser : : parse_match_expr ( )
{
auto rule_start = push_start ( ) ;
if ( ! expect ( " match " ) )
return nullptr ;
if ( consume_while ( is_whitespace ) . is_empty ( ) ) {
2020-09-28 13:57:20 +03:00
restore_to ( * rule_start ) ;
2020-09-14 18:02:21 +03:00
return nullptr ;
}
auto match_expression = parse_expression ( ) ;
if ( ! match_expression ) {
return create < AST : : MatchExpr > (
2020-12-01 12:25:14 +03:00
create < AST : : SyntaxError > ( " Expected an expression after 'match' " , true ) ,
2020-09-14 18:02:21 +03:00
String { } , Optional < AST : : Position > { } , Vector < AST : : MatchEntry > { } ) ;
}
consume_while ( is_any_of ( " \t \n " ) ) ;
String match_name ;
Optional < AST : : Position > as_position ;
auto as_start = m_offset ;
2020-09-28 13:57:20 +03:00
auto as_line = line ( ) ;
2020-09-14 18:02:21 +03:00
if ( expect ( " as " ) ) {
2020-09-28 13:57:20 +03:00
as_position = AST : : Position { as_start , m_offset , as_line , line ( ) } ;
2020-09-14 18:02:21 +03:00
if ( consume_while ( is_any_of ( " \t \n " ) ) . is_empty ( ) ) {
auto node = create < AST : : MatchExpr > (
2020-09-16 03:37:14 +03:00
match_expression . release_nonnull ( ) ,
2020-09-14 18:02:21 +03:00
String { } , move ( as_position ) , Vector < AST : : MatchEntry > { } ) ;
2020-12-01 12:25:14 +03:00
node - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected whitespace after 'as' in 'match' " , true ) ) ;
2020-09-14 18:02:21 +03:00
return node ;
}
match_name = consume_while ( is_word_character ) ;
if ( match_name . is_empty ( ) ) {
auto node = create < AST : : MatchExpr > (
2020-09-16 03:37:14 +03:00
match_expression . release_nonnull ( ) ,
2020-09-14 18:02:21 +03:00
String { } , move ( as_position ) , Vector < AST : : MatchEntry > { } ) ;
2020-12-01 12:25:14 +03:00
node - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected an identifier after 'as' in 'match' " , true ) ) ;
2020-09-14 18:02:21 +03:00
return node ;
}
}
consume_while ( is_any_of ( " \t \n " ) ) ;
if ( ! expect ( ' { ' ) ) {
auto node = create < AST : : MatchExpr > (
2020-09-16 03:37:14 +03:00
match_expression . release_nonnull ( ) ,
2020-09-14 18:02:21 +03:00
move ( match_name ) , move ( as_position ) , Vector < AST : : MatchEntry > { } ) ;
2020-12-01 12:25:14 +03:00
node - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected an open brace '{' to start a 'match' entry list " , true ) ) ;
2020-09-14 18:02:21 +03:00
return node ;
}
consume_while ( is_any_of ( " \t \n " ) ) ;
Vector < AST : : MatchEntry > entries ;
for ( ; ; ) {
auto entry = parse_match_entry ( ) ;
consume_while ( is_any_of ( " \t \n " ) ) ;
if ( entry . options . is_empty ( ) )
break ;
entries . append ( entry ) ;
}
consume_while ( is_any_of ( " \t \n " ) ) ;
if ( ! expect ( ' } ' ) ) {
auto node = create < AST : : MatchExpr > (
2020-09-16 03:37:14 +03:00
match_expression . release_nonnull ( ) ,
2020-09-14 18:02:21 +03:00
move ( match_name ) , move ( as_position ) , move ( entries ) ) ;
2020-12-01 12:25:14 +03:00
node - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected a close brace '}' to end a 'match' entry list " , true ) ) ;
2020-09-14 18:02:21 +03:00
return node ;
}
2020-09-16 03:37:14 +03:00
return create < AST : : MatchExpr > ( match_expression . release_nonnull ( ) , move ( match_name ) , move ( as_position ) , move ( entries ) ) ;
2020-09-14 18:02:21 +03:00
}
AST : : MatchEntry Parser : : parse_match_entry ( )
{
auto rule_start = push_start ( ) ;
NonnullRefPtrVector < AST : : Node > patterns ;
Vector < AST : : Position > pipe_positions ;
2020-10-25 10:12:03 +03:00
Optional < Vector < String > > match_names ;
Optional < AST : : Position > match_as_position ;
2020-09-14 18:02:21 +03:00
auto pattern = parse_match_pattern ( ) ;
if ( ! pattern )
2020-12-01 12:25:14 +03:00
return { { } , { } , { } , { } , create < AST : : SyntaxError > ( " Expected a pattern in 'match' body " , true ) } ;
2020-09-14 18:02:21 +03:00
patterns . append ( pattern . release_nonnull ( ) ) ;
consume_while ( is_any_of ( " \t \n " ) ) ;
auto previous_pipe_start_position = m_offset ;
2020-09-28 13:57:20 +03:00
auto previous_pipe_start_line = line ( ) ;
2020-09-14 18:02:21 +03:00
RefPtr < AST : : SyntaxError > error ;
while ( expect ( ' | ' ) ) {
2020-09-28 13:57:20 +03:00
pipe_positions . append ( { previous_pipe_start_position , m_offset , previous_pipe_start_line , line ( ) } ) ;
2020-09-14 18:02:21 +03:00
consume_while ( is_any_of ( " \t \n " ) ) ;
auto pattern = parse_match_pattern ( ) ;
if ( ! pattern ) {
2020-12-01 12:25:14 +03:00
error = create < AST : : SyntaxError > ( " Expected a pattern to follow '|' in 'match' body " , true ) ;
2020-09-14 18:02:21 +03:00
break ;
}
consume_while ( is_any_of ( " \t \n " ) ) ;
patterns . append ( pattern . release_nonnull ( ) ) ;
2020-09-28 13:57:20 +03:00
previous_pipe_start_line = line ( ) ;
previous_pipe_start_position = m_offset ;
2020-09-14 18:02:21 +03:00
}
consume_while ( is_any_of ( " \t \n " ) ) ;
2020-10-25 10:12:03 +03:00
auto as_start_position = m_offset ;
auto as_start_line = line ( ) ;
if ( expect ( " as " ) ) {
match_as_position = AST : : Position { as_start_position , m_offset , as_start_line , line ( ) } ;
consume_while ( is_any_of ( " \t \n " ) ) ;
if ( ! expect ( ' ( ' ) ) {
if ( ! error )
error = create < AST : : SyntaxError > ( " Expected an explicit list of identifiers after a pattern 'as' " ) ;
} else {
match_names = Vector < String > ( ) ;
for ( ; ; ) {
consume_while ( is_whitespace ) ;
auto name = consume_while ( is_word_character ) ;
if ( name . is_empty ( ) )
break ;
match_names . value ( ) . append ( move ( name ) ) ;
}
if ( ! expect ( ' ) ' ) ) {
if ( ! error )
2020-12-01 12:25:14 +03:00
error = create < AST : : SyntaxError > ( " Expected a close paren ')' to end the identifier list of pattern 'as' " , true ) ;
2020-10-25 10:12:03 +03:00
}
}
consume_while ( is_any_of ( " \t \n " ) ) ;
}
2020-09-14 18:02:21 +03:00
if ( ! expect ( ' { ' ) ) {
if ( ! error )
2020-12-01 12:25:14 +03:00
error = create < AST : : SyntaxError > ( " Expected an open brace '{' to start a match entry body " , true ) ;
2020-09-14 18:02:21 +03:00
}
auto body = parse_toplevel ( ) ;
if ( ! expect ( ' } ' ) ) {
if ( ! error )
2020-12-01 12:25:14 +03:00
error = create < AST : : SyntaxError > ( " Expected a close brace '}' to end a match entry body " , true ) ;
2020-09-14 18:02:21 +03:00
}
if ( body & & error )
body - > set_is_syntax_error ( * error ) ;
else if ( error )
body = error ;
2020-10-25 10:12:03 +03:00
return { move ( patterns ) , move ( match_names ) , move ( match_as_position ) , move ( pipe_positions ) , move ( body ) } ;
2020-09-14 18:02:21 +03:00
}
RefPtr < AST : : Node > Parser : : parse_match_pattern ( )
{
return parse_expression ( ) ;
}
2020-06-17 16:35:06 +03:00
RefPtr < AST : : Node > Parser : : parse_redirection ( )
{
auto rule_start = push_start ( ) ;
auto pipe_fd = 0 ;
auto number = consume_while ( is_digit ) ;
if ( number . is_empty ( ) ) {
pipe_fd = - 1 ;
} else {
auto fd = number . to_int ( ) ;
2020-11-29 15:48:13 +03:00
pipe_fd = fd . value_or ( - 1 ) ;
2020-06-17 16:35:06 +03:00
}
switch ( peek ( ) ) {
case ' > ' : {
consume ( ) ;
if ( peek ( ) = = ' > ' ) {
consume ( ) ;
consume_while ( is_whitespace ) ;
pipe_fd = pipe_fd > = 0 ? pipe_fd : STDOUT_FILENO ;
auto path = parse_expression ( ) ;
if ( ! path ) {
if ( ! at_end ( ) ) {
// Eat a character and hope the problem goes away
consume ( ) ;
2019-05-07 02:12:08 +03:00
}
2020-12-01 12:25:14 +03:00
path = create < AST : : SyntaxError > ( " Expected a path after redirection " , true ) ;
2019-05-07 02:12:08 +03:00
}
2020-09-16 03:37:14 +03:00
return create < AST : : WriteAppendRedirection > ( pipe_fd , path . release_nonnull ( ) ) ; // Redirection WriteAppend
2020-06-17 16:35:06 +03:00
}
if ( peek ( ) = = ' & ' ) {
consume ( ) ;
// FIXME: 'fd>&-' Syntax not the best. needs discussion.
if ( peek ( ) = = ' - ' ) {
consume ( ) ;
pipe_fd = pipe_fd > = 0 ? pipe_fd : STDOUT_FILENO ;
return create < AST : : CloseFdRedirection > ( pipe_fd ) ; // Redirection CloseFd
2019-05-07 02:12:08 +03:00
}
2020-06-17 16:35:06 +03:00
int dest_pipe_fd = 0 ;
auto number = consume_while ( is_digit ) ;
pipe_fd = pipe_fd > = 0 ? pipe_fd : STDOUT_FILENO ;
if ( number . is_empty ( ) ) {
dest_pipe_fd = - 1 ;
} else {
auto fd = number . to_int ( ) ;
2020-12-03 12:05:18 +03:00
dest_pipe_fd = fd . value_or ( - 1 ) ;
2019-09-14 12:36:09 +03:00
}
2020-06-28 17:12:57 +03:00
auto redir = create < AST : : Fd2FdRedirection > ( pipe_fd , dest_pipe_fd ) ; // Redirection Fd2Fd
if ( dest_pipe_fd = = - 1 )
redir - > set_is_syntax_error ( * create < AST : : SyntaxError > ( " Expected a file descriptor " ) ) ;
return redir ;
2020-06-17 16:35:06 +03:00
}
consume_while ( is_whitespace ) ;
pipe_fd = pipe_fd > = 0 ? pipe_fd : STDOUT_FILENO ;
auto path = parse_expression ( ) ;
if ( ! path ) {
if ( ! at_end ( ) ) {
// Eat a character and hope the problem goes away
consume ( ) ;
2019-05-07 02:12:08 +03:00
}
2020-12-01 12:25:14 +03:00
path = create < AST : : SyntaxError > ( " Expected a path after redirection " , true ) ;
2020-06-17 16:35:06 +03:00
}
2020-09-16 03:37:14 +03:00
return create < AST : : WriteRedirection > ( pipe_fd , path . release_nonnull ( ) ) ; // Redirection Write
2020-06-17 16:35:06 +03:00
}
case ' < ' : {
consume ( ) ;
enum {
Read ,
ReadWrite ,
} mode { Read } ;
if ( peek ( ) = = ' > ' ) {
mode = ReadWrite ;
consume ( ) ;
}
consume_while ( is_whitespace ) ;
pipe_fd = pipe_fd > = 0 ? pipe_fd : STDIN_FILENO ;
auto path = parse_expression ( ) ;
if ( ! path ) {
if ( ! at_end ( ) ) {
// Eat a character and hope the problem goes away
consume ( ) ;
2019-05-07 02:12:08 +03:00
}
2020-12-01 12:25:14 +03:00
path = create < AST : : SyntaxError > ( " Expected a path after redirection " , true ) ;
2020-06-17 16:35:06 +03:00
}
if ( mode = = Read )
2020-09-16 03:37:14 +03:00
return create < AST : : ReadRedirection > ( pipe_fd , path . release_nonnull ( ) ) ; // Redirection Read
2020-04-30 03:56:16 +03:00
2020-09-16 03:37:14 +03:00
return create < AST : : ReadWriteRedirection > ( pipe_fd , path . release_nonnull ( ) ) ; // Redirection ReadWrite
2020-06-17 16:35:06 +03:00
}
default :
2020-09-28 13:57:20 +03:00
restore_to ( * rule_start ) ;
2020-06-17 16:35:06 +03:00
return nullptr ;
}
}
2019-12-04 15:31:53 +03:00
2020-06-17 16:35:06 +03:00
RefPtr < AST : : Node > Parser : : parse_list_expression ( )
{
consume_while ( is_whitespace ) ;
2019-12-04 15:31:53 +03:00
2020-06-17 16:35:06 +03:00
auto rule_start = push_start ( ) ;
2020-09-16 03:37:14 +03:00
Vector < NonnullRefPtr < AST : : Node > > nodes ;
2019-12-04 15:31:53 +03:00
2020-07-12 00:11:24 +03:00
do {
auto expr = parse_expression ( ) ;
if ( ! expr )
break ;
2020-09-16 03:37:14 +03:00
nodes . append ( expr . release_nonnull ( ) ) ;
2020-07-12 00:11:24 +03:00
} while ( ! consume_while ( is_whitespace ) . is_empty ( ) ) ;
2019-12-04 15:31:53 +03:00
2020-07-12 00:11:24 +03:00
if ( nodes . is_empty ( ) )
return nullptr ;
2019-05-26 01:38:11 +03:00
2020-07-12 00:11:24 +03:00
return create < AST : : ListConcatenate > ( move ( nodes ) ) ; // Concatenate List
2020-06-17 16:35:06 +03:00
}
RefPtr < AST : : Node > Parser : : parse_expression ( )
{
auto rule_start = push_start ( ) ;
2020-11-30 17:02:48 +03:00
if ( m_rule_start_offsets . size ( ) > max_allowed_nested_rule_depth )
return create < AST : : SyntaxError > ( String : : formatted ( " Expression nested too deep (max allowed is {}) " , max_allowed_nested_rule_depth ) ) ;
2020-06-17 16:35:06 +03:00
auto starting_char = peek ( ) ;
2020-09-16 03:37:14 +03:00
auto read_concat = [ & ] ( auto & & expr ) - > NonnullRefPtr < AST : : Node > {
2020-06-20 16:30:45 +03:00
if ( is_whitespace ( peek ( ) ) )
2020-09-16 03:37:14 +03:00
return move ( expr ) ;
2020-06-20 16:30:45 +03:00
if ( auto next_expr = parse_expression ( ) )
2020-09-16 03:37:14 +03:00
return create < AST : : Juxtaposition > ( move ( expr ) , next_expr . release_nonnull ( ) ) ;
2020-06-20 16:30:45 +03:00
2020-09-16 03:37:14 +03:00
return move ( expr ) ;
2020-06-20 16:30:45 +03:00
} ;
2020-10-24 17:43:02 +03:00
if ( strchr ( " &|)} ;<> \n " , starting_char ) ! = nullptr )
return nullptr ;
2021-01-11 12:34:59 +03:00
if ( m_extra_chars_not_allowed_in_barewords . contains_slow ( starting_char ) )
2020-10-24 17:43:02 +03:00
return nullptr ;
if ( m_is_in_brace_expansion_spec & & next_is ( " .. " ) )
2020-06-17 16:35:06 +03:00
return nullptr ;
if ( isdigit ( starting_char ) ) {
ScopedValueRollback offset_rollback { m_offset } ;
auto redir = parse_redirection ( ) ;
if ( redir )
return nullptr ;
}
if ( starting_char = = ' $ ' ) {
if ( auto variable = parse_variable ( ) )
2020-09-16 03:37:14 +03:00
return read_concat ( variable . release_nonnull ( ) ) ;
2020-06-17 16:35:06 +03:00
2021-03-05 16:03:23 +03:00
if ( auto immediate = parse_immediate_expression ( ) )
return read_concat ( immediate . release_nonnull ( ) ) ;
2020-06-17 16:35:06 +03:00
if ( auto inline_exec = parse_evaluate ( ) )
2020-09-16 03:37:14 +03:00
return read_concat ( inline_exec . release_nonnull ( ) ) ;
2020-06-17 16:35:06 +03:00
}
if ( starting_char = = ' # ' )
return parse_comment ( ) ;
if ( starting_char = = ' ( ' ) {
consume ( ) ;
auto list = parse_list_expression ( ) ;
2020-06-22 14:07:20 +03:00
if ( ! expect ( ' ) ' ) ) {
2020-09-28 13:57:20 +03:00
restore_to ( * rule_start ) ;
2020-06-22 14:07:20 +03:00
return nullptr ;
}
2020-06-20 16:30:45 +03:00
return read_concat ( create < AST : : CastToList > ( move ( list ) ) ) ; // Cast To List
2020-06-17 16:35:06 +03:00
}
2021-03-05 15:44:53 +03:00
if ( starting_char = = ' ! ' & & m_in_interactive_mode ) {
2021-01-11 12:34:59 +03:00
if ( auto designator = parse_history_designator ( ) )
return designator ;
}
2020-09-16 03:37:14 +03:00
if ( auto composite = parse_string_composite ( ) )
return read_concat ( composite . release_nonnull ( ) ) ;
return nullptr ;
2020-06-17 16:35:06 +03:00
}
RefPtr < AST : : Node > Parser : : parse_string_composite ( )
{
auto rule_start = push_start ( ) ;
if ( auto string = parse_string ( ) ) {
if ( auto next_part = parse_string_composite ( ) )
2020-09-16 03:37:14 +03:00
return create < AST : : Juxtaposition > ( string . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate String StringComposite
2020-06-17 16:35:06 +03:00
return string ;
}
if ( auto variable = parse_variable ( ) ) {
if ( auto next_part = parse_string_composite ( ) )
2020-09-16 03:37:14 +03:00
return create < AST : : Juxtaposition > ( variable . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate Variable StringComposite
2020-06-17 16:35:06 +03:00
return variable ;
}
if ( auto glob = parse_glob ( ) ) {
if ( auto next_part = parse_string_composite ( ) )
2020-09-16 03:37:14 +03:00
return create < AST : : Juxtaposition > ( glob . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate Glob StringComposite
2020-06-17 16:35:06 +03:00
return glob ;
}
2020-10-24 17:43:02 +03:00
if ( auto expansion = parse_brace_expansion ( ) ) {
if ( auto next_part = parse_string_composite ( ) )
return create < AST : : Juxtaposition > ( expansion . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate BraceExpansion StringComposite
return expansion ;
}
2020-06-17 16:35:06 +03:00
if ( auto bareword = parse_bareword ( ) ) {
if ( auto next_part = parse_string_composite ( ) )
2020-09-16 03:37:14 +03:00
return create < AST : : Juxtaposition > ( bareword . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate Bareword StringComposite
2020-06-17 16:35:06 +03:00
return bareword ;
}
if ( auto inline_command = parse_evaluate ( ) ) {
if ( auto next_part = parse_string_composite ( ) )
2020-09-16 03:37:14 +03:00
return create < AST : : Juxtaposition > ( inline_command . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate Execute StringComposite
2020-06-17 16:35:06 +03:00
return inline_command ;
}
return nullptr ;
}
RefPtr < AST : : Node > Parser : : parse_string ( )
{
auto rule_start = push_start ( ) ;
if ( at_end ( ) )
return nullptr ;
if ( peek ( ) = = ' " ' ) {
consume ( ) ;
auto inner = parse_doublequoted_string_inner ( ) ;
2020-06-23 17:40:41 +03:00
if ( ! inner )
2020-12-01 12:25:14 +03:00
inner = create < AST : : SyntaxError > ( " Unexpected EOF in string " , true ) ;
2020-06-23 17:40:41 +03:00
if ( ! expect ( ' " ' ) ) {
inner = create < AST : : DoubleQuotedString > ( move ( inner ) ) ;
2020-12-01 12:25:14 +03:00
inner - > set_is_syntax_error ( * create < AST : : SyntaxError > ( " Expected a terminating double quote " , true ) ) ;
2020-06-23 17:40:41 +03:00
return inner ;
}
2020-06-17 16:35:06 +03:00
return create < AST : : DoubleQuotedString > ( move ( inner ) ) ; // Double Quoted String
}
if ( peek ( ) = = ' \' ' ) {
consume ( ) ;
auto text = consume_while ( is_not ( ' \' ' ) ) ;
2020-06-23 17:40:41 +03:00
bool is_error = false ;
2020-06-17 16:35:06 +03:00
if ( ! expect ( ' \' ' ) )
2020-06-23 17:40:41 +03:00
is_error = true ;
auto result = create < AST : : StringLiteral > ( move ( text ) ) ; // String Literal
if ( is_error )
2020-12-01 12:25:14 +03:00
result - > set_is_syntax_error ( * create < AST : : SyntaxError > ( " Expected a terminating single quote " , true ) ) ;
2021-03-17 18:30:02 +03:00
return result ;
2020-06-17 16:35:06 +03:00
}
return nullptr ;
}
RefPtr < AST : : Node > Parser : : parse_doublequoted_string_inner ( )
{
auto rule_start = push_start ( ) ;
if ( at_end ( ) )
return nullptr ;
StringBuilder builder ;
while ( ! at_end ( ) & & peek ( ) ! = ' " ' ) {
if ( peek ( ) = = ' \\ ' ) {
consume ( ) ;
if ( at_end ( ) ) {
2019-05-07 02:12:08 +03:00
break ;
}
2020-06-17 16:35:06 +03:00
auto ch = consume ( ) ;
switch ( ch ) {
case ' \\ ' :
default :
builder . append ( ch ) ;
2019-05-07 02:12:08 +03:00
break ;
2020-06-17 16:35:06 +03:00
case ' x ' : {
if ( m_input . length ( ) < = m_offset + 2 )
break ;
auto first_nibble = tolower ( consume ( ) ) ;
auto second_nibble = tolower ( consume ( ) ) ;
if ( ! isxdigit ( first_nibble ) | | ! isxdigit ( second_nibble ) ) {
builder . append ( first_nibble ) ;
builder . append ( second_nibble ) ;
break ;
2019-05-07 02:12:08 +03:00
}
2020-06-17 16:35:06 +03:00
builder . append ( to_byte ( first_nibble , second_nibble ) ) ;
2020-04-30 03:56:16 +03:00
break ;
}
2020-06-17 16:35:06 +03:00
case ' a ' :
builder . append ( ' \a ' ) ;
2020-04-30 03:56:16 +03:00
break ;
2020-06-17 16:35:06 +03:00
case ' b ' :
builder . append ( ' \b ' ) ;
2019-05-07 02:12:08 +03:00
break ;
2020-06-17 16:35:06 +03:00
case ' e ' :
builder . append ( ' \x1b ' ) ;
2019-05-07 02:12:08 +03:00
break ;
2020-06-17 16:35:06 +03:00
case ' f ' :
builder . append ( ' \f ' ) ;
2019-05-07 02:12:08 +03:00
break ;
2020-06-17 16:35:06 +03:00
case ' r ' :
builder . append ( ' \r ' ) ;
break ;
case ' n ' :
builder . append ( ' \n ' ) ;
2019-05-07 02:12:08 +03:00
break ;
}
2020-06-17 16:35:06 +03:00
continue ;
}
if ( peek ( ) = = ' $ ' ) {
auto string_literal = create < AST : : StringLiteral > ( builder . to_string ( ) ) ; // String Literal
2021-03-05 16:03:23 +03:00
auto read_concat = [ & ] ( auto & & node ) {
2020-06-17 16:35:06 +03:00
auto inner = create < AST : : StringPartCompose > (
move ( string_literal ) ,
2021-03-05 16:03:23 +03:00
move ( node ) ) ; // Compose String Node
2020-06-17 16:35:06 +03:00
if ( auto string = parse_doublequoted_string_inner ( ) ) {
2020-09-16 03:37:14 +03:00
return create < AST : : StringPartCompose > ( move ( inner ) , string . release_nonnull ( ) ) ; // Compose Composition Composition
2019-09-14 12:36:09 +03:00
}
2020-06-17 16:35:06 +03:00
return inner ;
2021-03-05 16:03:23 +03:00
} ;
2020-06-17 16:35:06 +03:00
2021-03-05 16:03:23 +03:00
if ( auto variable = parse_variable ( ) )
return read_concat ( variable . release_nonnull ( ) ) ;
2020-06-17 16:35:06 +03:00
2021-03-05 16:03:23 +03:00
if ( auto immediate = parse_immediate_expression ( ) )
return read_concat ( immediate . release_nonnull ( ) ) ;
2020-06-17 16:35:06 +03:00
2021-03-05 16:03:23 +03:00
if ( auto evaluate = parse_evaluate ( ) )
return read_concat ( evaluate . release_nonnull ( ) ) ;
2020-06-17 16:35:06 +03:00
}
builder . append ( consume ( ) ) ;
}
return create < AST : : StringLiteral > ( builder . to_string ( ) ) ; // String Literal
}
RefPtr < AST : : Node > Parser : : parse_variable ( )
{
auto rule_start = push_start ( ) ;
if ( at_end ( ) )
return nullptr ;
if ( peek ( ) ! = ' $ ' )
return nullptr ;
consume ( ) ;
switch ( peek ( ) ) {
case ' $ ' :
case ' ? ' :
2020-08-04 07:57:25 +03:00
case ' * ' :
case ' # ' :
2020-06-17 16:35:06 +03:00
return create < AST : : SpecialVariable > ( consume ( ) ) ; // Variable Special
default :
break ;
}
auto name = consume_while ( is_word_character ) ;
if ( name . length ( ) = = 0 ) {
2020-09-28 13:57:20 +03:00
restore_to ( rule_start - > offset , rule_start - > line ) ;
2020-06-17 16:35:06 +03:00
return nullptr ;
2019-05-07 02:12:08 +03:00
}
2020-04-30 03:56:16 +03:00
2020-06-17 16:35:06 +03:00
return create < AST : : SimpleVariable > ( move ( name ) ) ; // Variable Simple
}
RefPtr < AST : : Node > Parser : : parse_evaluate ( )
{
auto rule_start = push_start ( ) ;
if ( at_end ( ) )
return nullptr ;
if ( peek ( ) ! = ' $ ' )
return nullptr ;
consume ( ) ;
2020-06-21 23:00:14 +03:00
if ( peek ( ) = = ' ( ' ) {
consume ( ) ;
auto inner = parse_pipe_sequence ( ) ;
2020-06-23 17:40:41 +03:00
if ( ! inner )
2020-12-01 12:25:14 +03:00
inner = create < AST : : SyntaxError > ( " Unexpected EOF in list " , true ) ;
2020-06-23 17:40:41 +03:00
if ( ! expect ( ' ) ' ) )
2020-12-01 12:25:14 +03:00
inner - > set_is_syntax_error ( * create < AST : : SyntaxError > ( " Expected a terminating close paren " , true ) ) ;
2020-09-16 03:37:14 +03:00
return create < AST : : Execute > ( inner . release_nonnull ( ) , true ) ;
2020-06-21 23:00:14 +03:00
}
2020-06-17 16:35:06 +03:00
auto inner = parse_expression ( ) ;
if ( ! inner ) {
2020-12-01 12:25:14 +03:00
inner = create < AST : : SyntaxError > ( " Expected a command " , true ) ;
2020-06-17 16:35:06 +03:00
} else {
if ( inner - > is_list ( ) ) {
2020-09-16 03:37:14 +03:00
auto execute_inner = create < AST : : Execute > ( inner . release_nonnull ( ) , true ) ;
inner = move ( execute_inner ) ;
2020-05-10 09:05:23 +03:00
} else {
2020-09-16 03:37:14 +03:00
auto dyn_inner = create < AST : : DynamicEvaluate > ( inner . release_nonnull ( ) ) ;
inner = move ( dyn_inner ) ;
2020-05-10 09:05:23 +03:00
}
2020-04-30 03:56:16 +03:00
}
2020-06-17 16:35:06 +03:00
return inner ;
}
2019-05-07 02:12:08 +03:00
2021-03-05 16:03:23 +03:00
RefPtr < AST : : Node > Parser : : parse_immediate_expression ( )
{
auto rule_start = push_start ( ) ;
if ( at_end ( ) )
return nullptr ;
if ( peek ( ) ! = ' $ ' )
return nullptr ;
consume ( ) ;
if ( peek ( ) ! = ' { ' ) {
restore_to ( * rule_start ) ;
return nullptr ;
}
consume ( ) ;
consume_while ( is_whitespace ) ;
auto function_name_start_offset = current_position ( ) ;
auto function_name = consume_while ( is_word_character ) ;
auto function_name_end_offset = current_position ( ) ;
AST : : Position function_position {
function_name_start_offset . offset ,
function_name_end_offset . offset ,
function_name_start_offset . line ,
function_name_end_offset . line ,
} ;
consume_while ( is_whitespace ) ;
NonnullRefPtrVector < AST : : Node > arguments ;
do {
auto expr = parse_expression ( ) ;
if ( ! expr )
break ;
arguments . append ( expr . release_nonnull ( ) ) ;
} while ( ! consume_while ( is_whitespace ) . is_empty ( ) ) ;
auto ending_brace_start_offset = current_position ( ) ;
if ( peek ( ) = = ' } ' )
consume ( ) ;
auto ending_brace_end_offset = current_position ( ) ;
auto ending_brace_position = ending_brace_start_offset . offset = = ending_brace_end_offset . offset
? Optional < AST : : Position > { }
: Optional < AST : : Position > {
AST : : Position {
ending_brace_start_offset . offset ,
ending_brace_end_offset . offset ,
ending_brace_start_offset . line ,
ending_brace_end_offset . line ,
}
} ;
auto node = create < AST : : ImmediateExpression > (
AST : : NameWithPosition { function_name , move ( function_position ) } ,
move ( arguments ) ,
ending_brace_position ) ;
if ( ! ending_brace_position . has_value ( ) )
node - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected a closing brace '}' to end an immediate expression " , true ) ) ;
else if ( node - > function_name ( ) . is_empty ( ) )
node - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected an immediate function name " ) ) ;
return node ;
}
2021-01-11 12:34:59 +03:00
RefPtr < AST : : Node > Parser : : parse_history_designator ( )
{
auto rule_start = push_start ( ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( peek ( ) = = ' ! ' ) ;
2021-01-11 12:34:59 +03:00
consume ( ) ;
// Event selector
AST : : HistorySelector selector ;
2021-02-07 02:12:57 +03:00
RefPtr < AST : : SyntaxError > syntax_error ;
2021-01-11 12:34:59 +03:00
selector . event . kind = AST : : HistorySelector : : EventKind : : StartingStringLookup ;
selector . event . text_position = { m_offset , m_offset , m_line , m_line } ;
selector . word_selector_range = {
AST : : HistorySelector : : WordSelector {
2021-02-02 19:50:05 +03:00
AST : : HistorySelector : : WordSelectorKind : : Index ,
0 ,
{ m_offset , m_offset , m_line , m_line } ,
nullptr } ,
AST : : HistorySelector : : WordSelector {
AST : : HistorySelector : : WordSelectorKind : : Last ,
0 ,
{ m_offset , m_offset , m_line , m_line } ,
nullptr }
2021-01-11 12:34:59 +03:00
} ;
switch ( peek ( ) ) {
case ' ! ' :
consume ( ) ;
selector . event . kind = AST : : HistorySelector : : EventKind : : IndexFromEnd ;
selector . event . index = 0 ;
selector . event . text = " ! " ;
break ;
case ' ? ' :
consume ( ) ;
selector . event . kind = AST : : HistorySelector : : EventKind : : ContainingStringLookup ;
[[fallthrough]] ;
default : {
TemporaryChange chars_change { m_extra_chars_not_allowed_in_barewords , { ' : ' } } ;
auto bareword = parse_bareword ( ) ;
if ( ! bareword | | ! bareword - > is_bareword ( ) ) {
restore_to ( * rule_start ) ;
return nullptr ;
}
selector . event . text = static_ptr_cast < AST : : BarewordLiteral > ( bareword ) - > text ( ) ;
2021-02-07 02:12:57 +03:00
selector . event . text_position = bareword - > position ( ) ;
2021-01-11 12:34:59 +03:00
auto it = selector . event . text . begin ( ) ;
bool is_negative = false ;
if ( * it = = ' - ' ) {
+ + it ;
is_negative = true ;
}
2021-02-25 23:10:47 +03:00
if ( it ! = selector . event . text . end ( ) & & all_of ( it , selector . event . text . end ( ) , is_digit ) ) {
2021-01-11 12:34:59 +03:00
if ( is_negative )
selector . event . kind = AST : : HistorySelector : : EventKind : : IndexFromEnd ;
else
selector . event . kind = AST : : HistorySelector : : EventKind : : IndexFromStart ;
2021-02-07 02:12:57 +03:00
auto number = selector . event . text . to_int ( ) ;
if ( number . has_value ( ) )
selector . event . index = abs ( number . value ( ) ) ;
else
syntax_error = create < AST : : SyntaxError > ( " History entry index value invalid or out of range " ) ;
2021-01-11 12:34:59 +03:00
}
break ;
}
}
2021-02-07 02:12:57 +03:00
if ( peek ( ) ! = ' : ' ) {
auto node = create < AST : : HistoryEvent > ( move ( selector ) ) ;
if ( syntax_error )
node - > set_is_syntax_error ( * syntax_error ) ;
return node ;
}
2021-01-11 12:34:59 +03:00
consume ( ) ;
// Word selectors
auto parse_word_selector = [ & ] ( ) - > Optional < AST : : HistorySelector : : WordSelector > {
auto rule_start = push_start ( ) ;
auto c = peek ( ) ;
if ( isdigit ( c ) ) {
auto num = consume_while ( is_digit ) ;
auto value = num . to_uint ( ) ;
2021-02-02 19:50:05 +03:00
if ( ! value . has_value ( ) ) {
return AST : : HistorySelector : : WordSelector {
AST : : HistorySelector : : WordSelectorKind : : Index ,
0 ,
{ m_rule_start_offsets . last ( ) , m_offset , m_rule_start_lines . last ( ) , line ( ) } ,
2021-02-07 02:12:57 +03:00
syntax_error ? NonnullRefPtr ( * syntax_error ) : create < AST : : SyntaxError > ( " Word selector value invalid or out of range " )
2021-02-02 19:50:05 +03:00
} ;
}
2021-01-11 12:34:59 +03:00
return AST : : HistorySelector : : WordSelector {
AST : : HistorySelector : : WordSelectorKind : : Index ,
value . value ( ) ,
2021-02-02 19:50:05 +03:00
{ m_rule_start_offsets . last ( ) , m_offset , m_rule_start_lines . last ( ) , line ( ) } ,
2021-02-07 02:12:57 +03:00
syntax_error
2021-01-11 12:34:59 +03:00
} ;
}
if ( c = = ' ^ ' ) {
consume ( ) ;
return AST : : HistorySelector : : WordSelector {
AST : : HistorySelector : : WordSelectorKind : : Index ,
0 ,
2021-02-02 19:50:05 +03:00
{ m_rule_start_offsets . last ( ) , m_offset , m_rule_start_lines . last ( ) , line ( ) } ,
2021-02-07 02:12:57 +03:00
syntax_error
2021-01-11 12:34:59 +03:00
} ;
}
if ( c = = ' $ ' ) {
consume ( ) ;
return AST : : HistorySelector : : WordSelector {
AST : : HistorySelector : : WordSelectorKind : : Last ,
0 ,
2021-02-02 19:50:05 +03:00
{ m_rule_start_offsets . last ( ) , m_offset , m_rule_start_lines . last ( ) , line ( ) } ,
2021-02-07 02:12:57 +03:00
syntax_error
2021-01-11 12:34:59 +03:00
} ;
}
return { } ;
} ;
auto start = parse_word_selector ( ) ;
if ( ! start . has_value ( ) ) {
2021-02-07 02:12:57 +03:00
if ( ! syntax_error )
syntax_error = create < AST : : SyntaxError > ( " Expected a word selector after ':' in a history event designator " , true ) ;
2021-01-11 12:34:59 +03:00
auto node = create < AST : : HistoryEvent > ( move ( selector ) ) ;
2021-02-07 02:12:57 +03:00
node - > set_is_syntax_error ( * syntax_error ) ;
2021-01-11 12:34:59 +03:00
return node ;
}
selector . word_selector_range . start = start . release_value ( ) ;
if ( peek ( ) = = ' - ' ) {
consume ( ) ;
auto end = parse_word_selector ( ) ;
if ( ! end . has_value ( ) ) {
2021-02-07 02:12:57 +03:00
if ( ! syntax_error )
syntax_error = create < AST : : SyntaxError > ( " Expected a word selector after '-' in a history event designator word selector " , true ) ;
2021-01-11 12:34:59 +03:00
auto node = create < AST : : HistoryEvent > ( move ( selector ) ) ;
2021-02-07 02:12:57 +03:00
node - > set_is_syntax_error ( * syntax_error ) ;
2021-01-11 12:34:59 +03:00
return node ;
}
selector . word_selector_range . end = move ( end ) ;
} else {
selector . word_selector_range . end . clear ( ) ;
}
2021-02-07 02:12:57 +03:00
auto node = create < AST : : HistoryEvent > ( move ( selector ) ) ;
if ( syntax_error )
node - > set_is_syntax_error ( * syntax_error ) ;
return node ;
2021-01-11 12:34:59 +03:00
}
2020-06-17 16:35:06 +03:00
RefPtr < AST : : Node > Parser : : parse_comment ( )
{
if ( at_end ( ) )
return nullptr ;
if ( peek ( ) ! = ' # ' )
return nullptr ;
consume ( ) ;
auto text = consume_while ( is_not ( ' \n ' ) ) ;
return create < AST : : Comment > ( move ( text ) ) ; // Comment
}
RefPtr < AST : : Node > Parser : : parse_bareword ( )
{
auto rule_start = push_start ( ) ;
StringBuilder builder ;
2020-10-24 17:43:02 +03:00
auto is_acceptable_bareword_character = [ & ] ( char c ) {
return strchr ( " \\ \" '*$&#|() { } ? ; < > \ n " , c) == nullptr
2021-01-11 12:34:59 +03:00
& & ! m_extra_chars_not_allowed_in_barewords . contains_slow ( c ) ;
2020-06-17 16:35:06 +03:00
} ;
while ( ! at_end ( ) ) {
char ch = peek ( ) ;
if ( ch = = ' \\ ' ) {
consume ( ) ;
if ( ! at_end ( ) ) {
ch = consume ( ) ;
if ( is_acceptable_bareword_character ( ch ) )
builder . append ( ' \\ ' ) ;
2019-05-07 02:12:08 +03:00
}
2020-06-17 16:35:06 +03:00
builder . append ( ch ) ;
continue ;
2019-05-07 02:12:08 +03:00
}
2020-06-17 16:35:06 +03:00
2020-10-24 17:43:02 +03:00
if ( m_is_in_brace_expansion_spec & & next_is ( " .. " ) ) {
// Don't eat '..' in a brace expansion spec.
break ;
}
2020-06-17 16:35:06 +03:00
if ( is_acceptable_bareword_character ( ch ) ) {
builder . append ( consume ( ) ) ;
continue ;
}
break ;
}
if ( builder . is_empty ( ) )
return nullptr ;
2020-06-23 17:40:41 +03:00
auto current_end = m_offset ;
2020-09-28 13:57:20 +03:00
auto current_line = line ( ) ;
2020-06-17 16:35:06 +03:00
auto string = builder . to_string ( ) ;
if ( string . starts_with ( ' ~ ' ) ) {
String username ;
2020-06-23 17:40:41 +03:00
RefPtr < AST : : Node > tilde , text ;
2020-06-17 16:35:06 +03:00
auto first_slash_index = string . index_of ( " / " ) ;
if ( first_slash_index . has_value ( ) ) {
username = string . substring_view ( 1 , first_slash_index . value ( ) - 1 ) ;
string = string . substring_view ( first_slash_index . value ( ) , string . length ( ) - first_slash_index . value ( ) ) ;
} else {
username = string . substring_view ( 1 , string . length ( ) - 1 ) ;
string = " " ;
}
2020-06-23 17:40:41 +03:00
// Synthesize a Tilde Node with the correct positioning information.
{
2020-09-28 13:57:20 +03:00
restore_to ( rule_start - > offset , rule_start - > line ) ;
auto ch = consume ( ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( ch = = ' ~ ' ) ;
2021-03-15 10:55:20 +03:00
auto username_length = username . length ( ) ;
2020-06-23 17:40:41 +03:00
tilde = create < AST : : Tilde > ( move ( username ) ) ;
2021-03-15 10:55:20 +03:00
// Consume the username (if any)
for ( size_t i = 0 ; i < username_length ; + + i )
consume ( ) ;
2020-06-23 17:40:41 +03:00
}
2020-06-17 16:35:06 +03:00
if ( string . is_empty ( ) )
return tilde ;
2020-06-23 17:40:41 +03:00
// Synthesize a BarewordLiteral Node with the correct positioning information.
{
auto text_start = push_start ( ) ;
2020-09-28 13:57:20 +03:00
restore_to ( current_end , current_line ) ;
2020-06-23 17:40:41 +03:00
text = create < AST : : BarewordLiteral > ( move ( string ) ) ;
}
2020-10-03 00:14:37 +03:00
return create < AST : : Juxtaposition > ( tilde . release_nonnull ( ) , text . release_nonnull ( ) ) ; // Juxtaposition Variable Bareword
2019-05-07 02:12:08 +03:00
}
2020-06-17 16:35:06 +03:00
if ( string . starts_with ( " \\ ~ " ) ) {
// Un-escape the tilde, but only at the start (where it would be an expansion)
2020-06-23 17:40:41 +03:00
string = string . substring ( 1 , string . length ( ) - 1 ) ;
2020-06-17 16:35:06 +03:00
}
2020-06-23 17:40:41 +03:00
return create < AST : : BarewordLiteral > ( move ( string ) ) ; // Bareword Literal
2020-06-17 16:35:06 +03:00
}
RefPtr < AST : : Node > Parser : : parse_glob ( )
{
auto rule_start = push_start ( ) ;
auto bareword_part = parse_bareword ( ) ;
if ( at_end ( ) )
return bareword_part ;
char ch = peek ( ) ;
if ( ch = = ' * ' | | ch = = ' ? ' ) {
2020-09-28 13:57:20 +03:00
auto saved_offset = save_offset ( ) ;
2020-06-17 16:35:06 +03:00
consume ( ) ;
StringBuilder textbuilder ;
if ( bareword_part ) {
StringView text ;
2020-06-23 17:40:41 +03:00
if ( bareword_part - > is_bareword ( ) ) {
2020-06-17 16:35:06 +03:00
auto bareword = static_cast < AST : : BarewordLiteral * > ( bareword_part . ptr ( ) ) ;
text = bareword - > text ( ) ;
} else {
2020-06-23 17:40:41 +03:00
// FIXME: Allow composition of tilde+bareword with globs: '~/foo/bar/baz*'
2020-09-28 13:57:20 +03:00
restore_to ( saved_offset . offset , saved_offset . line ) ;
2020-06-28 17:12:57 +03:00
bareword_part - > set_is_syntax_error ( * create < AST : : SyntaxError > ( String : : format ( " Unexpected %s inside a glob " , bareword_part - > class_name ( ) . characters ( ) ) ) ) ;
2020-06-23 17:40:41 +03:00
return bareword_part ;
2020-06-17 16:35:06 +03:00
}
textbuilder . append ( text ) ;
}
textbuilder . append ( ch ) ;
auto glob_after = parse_glob ( ) ;
if ( glob_after ) {
if ( glob_after - > is_glob ( ) ) {
2020-12-03 12:03:06 +03:00
auto glob = static_cast < AST : : Glob * > ( glob_after . ptr ( ) ) ;
2020-06-17 16:35:06 +03:00
textbuilder . append ( glob - > text ( ) ) ;
} else if ( glob_after - > is_bareword ( ) ) {
auto bareword = static_cast < AST : : BarewordLiteral * > ( glob_after . ptr ( ) ) ;
textbuilder . append ( bareword - > text ( ) ) ;
2020-11-29 15:48:13 +03:00
} else if ( glob_after - > is_tilde ( ) ) {
auto bareword = static_cast < AST : : Tilde * > ( glob_after . ptr ( ) ) ;
textbuilder . append ( " ~ " ) ;
textbuilder . append ( bareword - > text ( ) ) ;
2020-06-17 16:35:06 +03:00
} else {
2020-11-29 15:48:13 +03:00
return create < AST : : SyntaxError > ( String : : formatted ( " Invalid node '{}' in glob position, escape shell special characters " , glob_after - > class_name ( ) ) ) ;
2020-06-17 16:35:06 +03:00
}
}
return create < AST : : Glob > ( textbuilder . to_string ( ) ) ; // Glob
}
return bareword_part ;
}
2020-10-24 17:43:02 +03:00
RefPtr < AST : : Node > Parser : : parse_brace_expansion ( )
{
auto rule_start = push_start ( ) ;
if ( ! expect ( ' { ' ) )
return nullptr ;
if ( auto spec = parse_brace_expansion_spec ( ) ) {
if ( ! expect ( ' } ' ) )
2020-12-01 12:25:14 +03:00
spec - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected a close brace '}' to end a brace expansion " , true ) ) ;
2020-10-24 17:43:02 +03:00
return spec ;
}
restore_to ( * rule_start ) ;
return nullptr ;
}
RefPtr < AST : : Node > Parser : : parse_brace_expansion_spec ( )
{
TemporaryChange is_in_brace_expansion { m_is_in_brace_expansion_spec , true } ;
2021-01-11 12:34:59 +03:00
TemporaryChange chars_change { m_extra_chars_not_allowed_in_barewords , { ' , ' } } ;
2020-10-24 17:43:02 +03:00
auto rule_start = push_start ( ) ;
auto start_expr = parse_expression ( ) ;
if ( start_expr ) {
if ( expect ( " .. " ) ) {
if ( auto end_expr = parse_expression ( ) ) {
if ( end_expr - > position ( ) . start_offset ! = start_expr - > position ( ) . end_offset + 2 )
end_expr - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected no whitespace between '..' and the following expression in brace expansion " ) ) ;
return create < AST : : Range > ( start_expr . release_nonnull ( ) , end_expr . release_nonnull ( ) ) ;
}
2020-12-01 12:25:14 +03:00
return create < AST : : Range > ( start_expr . release_nonnull ( ) , create < AST : : SyntaxError > ( " Expected an expression to end range brace expansion with " , true ) ) ;
2020-10-24 17:43:02 +03:00
}
}
NonnullRefPtrVector < AST : : Node > subexpressions ;
if ( start_expr )
subexpressions . append ( start_expr . release_nonnull ( ) ) ;
while ( expect ( ' , ' ) ) {
auto expr = parse_expression ( ) ;
if ( expr ) {
subexpressions . append ( expr . release_nonnull ( ) ) ;
} else {
subexpressions . append ( create < AST : : StringLiteral > ( " " ) ) ;
}
}
if ( subexpressions . is_empty ( ) )
return nullptr ;
return create < AST : : BraceExpansion > ( move ( subexpressions ) ) ;
}
2020-06-17 16:35:06 +03:00
StringView Parser : : consume_while ( Function < bool ( char ) > condition )
{
2020-11-29 15:48:13 +03:00
if ( at_end ( ) )
return { } ;
2020-06-17 16:35:06 +03:00
auto start_offset = m_offset ;
while ( ! at_end ( ) & & condition ( peek ( ) ) )
consume ( ) ;
return m_input . substring_view ( start_offset , m_offset - start_offset ) ;
2019-05-07 02:12:08 +03:00
}
2020-10-01 17:43:01 +03:00
2020-10-24 17:43:02 +03:00
bool Parser : : next_is ( const StringView & next )
{
auto start = push_start ( ) ;
auto res = expect ( next ) ;
restore_to ( * start ) ;
return res ;
}
2020-10-01 17:43:01 +03:00
}