2020-01-18 08:38:21 +00:00
/*
2021-07-30 10:55:57 +00:00
* Copyright ( c ) 2020 - 2021 , the SerenityOS developers .
2020-01-18 08:38:21 +00:00
*
2021-04-22 08:24:48 +00:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 08:38:21 +00:00
*/
2019-05-06 23:12:08 +00:00
# include "Parser.h"
2021-01-11 09:34:59 +00:00
# include "Shell.h"
# include <AK/AllOf.h>
2021-08-18 11:22:52 +00:00
# include <AK/GenericLexer.h>
2021-04-29 02:34:00 +00:00
# include <AK/ScopeGuard.h>
2021-03-12 16:29:37 +00:00
# include <AK/ScopedValueRollback.h>
2020-10-24 14:43:02 +00:00
# include <AK/TemporaryChange.h>
2020-04-30 00:56:16 +00:00
# include <ctype.h>
2019-05-06 23:12:08 +00:00
# include <stdio.h>
# include <unistd.h>
2020-10-01 14:43:01 +00:00
namespace Shell {
2020-09-28 10:57:20 +00:00
Parser : : SavedOffset Parser : : save_offset ( ) const
{
return { m_offset , m_line } ;
}
2020-06-17 13:35:06 +00:00
char Parser : : peek ( )
2019-05-06 23:12:08 +00:00
{
2020-11-29 12:48:13 +00:00
if ( at_end ( ) )
2020-06-17 13:35:06 +00:00
return 0 ;
2021-02-23 19:42:32 +00:00
VERIFY ( m_offset < m_input . length ( ) ) ;
2020-09-16 00:41:09 +00: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 10:57:20 +00:00
+ + m_line . line_number ;
m_line . line_column = 0 ;
2020-09-16 00:41:09 +00:00
return peek ( ) ;
}
return ch ;
2020-06-17 13:35:06 +00:00
}
char Parser : : consume ( )
{
2020-11-29 12:48:13 +00:00
if ( at_end ( ) )
return 0 ;
2020-06-17 13:35:06 +00:00
auto ch = peek ( ) ;
+ + m_offset ;
2020-09-16 00:41:09 +00:00
2020-09-28 10:57:20 +00:00
if ( ch = = ' \n ' ) {
+ + m_line . line_number ;
m_line . line_column = 0 ;
} else {
+ + m_line . line_column ;
}
2020-06-17 13:35:06 +00:00
2020-09-28 10:57:20 +00:00
return ch ;
2020-06-17 13:35:06 +00:00
}
bool Parser : : expect ( char ch )
{
return expect ( StringView { & ch , 1 } ) ;
}
bool Parser : : expect ( const StringView & expected )
{
2020-09-14 15:01:47 +00:00
auto offset_at_start = m_offset ;
2020-09-28 10:57:20 +00:00
auto line_at_start = line ( ) ;
2020-09-14 15:01:47 +00:00
2020-06-17 13:35:06 +00:00
if ( expected . length ( ) + m_offset > m_input . length ( ) )
return false ;
2021-03-05 13:03:23 +00:00
for ( auto & c : expected ) {
if ( peek ( ) ! = c ) {
2020-09-28 10:57:20 +00:00
restore_to ( offset_at_start , line_at_start ) ;
2020-06-17 13:35:06 +00:00
return false ;
2020-09-14 15:01:47 +00:00
}
2020-06-17 13:35:06 +00:00
consume ( ) ;
2019-05-06 23:12:08 +00:00
}
2020-06-17 13:35:06 +00:00
return true ;
}
template < typename A , typename . . . Args >
2020-08-04 16:16:37 +00:00
NonnullRefPtr < A > Parser : : create ( Args . . . args )
2019-05-06 23:12:08 +00:00
{
2021-04-23 14:46:57 +00:00
return adopt_ref ( * new A ( AST : : Position { m_rule_start_offsets . last ( ) , m_offset , m_rule_start_lines . last ( ) , line ( ) } , args . . . ) ) ;
2019-05-06 23:12:08 +00:00
}
2020-06-17 13:35:06 +00:00
[[nodiscard]] OwnPtr < Parser : : ScopedOffset > Parser : : push_start ( )
2019-08-30 04:54:05 +00:00
{
2020-09-28 10:57:20 +00: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 04:54:05 +00:00
}
2021-01-16 19:50:52 +00:00
Parser : : Offset Parser : : current_position ( )
{
return Offset { m_offset , { m_line . line_number , m_line . line_column } } ;
}
2020-06-17 13:35:06 +00:00
static constexpr bool is_whitespace ( char c )
2019-05-06 23:12:08 +00:00
{
2020-06-17 13:35:06 +00:00
return c = = ' ' | | c = = ' \t ' ;
2019-05-06 23:12:08 +00:00
}
2020-06-17 13:35:06 +00:00
static constexpr bool is_digit ( char c )
2019-05-06 23:12:08 +00:00
{
2020-06-17 13:35:06 +00:00
return c < = ' 9 ' & & c > = ' 0 ' ;
2019-05-06 23:12:08 +00:00
}
2020-06-17 13:35:06 +00:00
static constexpr auto is_not ( char c )
2020-04-30 00:56:16 +00:00
{
2020-06-17 13:35:06 +00:00
return [ c ] ( char ch ) { return ch ! = c ; } ;
2020-04-30 00:56:16 +00:00
}
2020-06-17 13:35:06 +00:00
static inline char to_byte ( char a , char b )
2019-05-06 23:12:08 +00:00
{
2020-06-17 13:35:06 +00:00
char buf [ 3 ] { a , b , 0 } ;
return strtol ( buf , nullptr , 16 ) ;
}
2020-05-24 18:30:46 +00:00
2020-06-17 13:35:06 +00:00
RefPtr < AST : : Node > Parser : : parse ( )
{
m_offset = 0 ;
2020-09-28 10:57:20 +00:00
m_line = { 0 , 0 } ;
2020-05-24 18:30:46 +00:00
2020-06-23 14:40:41 +00: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 12:48:13 +00:00
while ( ! at_end ( ) )
consume ( ) ;
2020-07-07 12:46:01 +00:00
auto syntax_error_node = create < AST : : SyntaxError > ( " Unexpected tokens past the end " ) ;
2020-09-16 00:37:14 +00:00
if ( ! toplevel )
toplevel = move ( syntax_error_node ) ;
2020-12-01 09:25:14 +00:00
else if ( ! toplevel - > is_syntax_error ( ) )
2020-09-16 00:37:14 +00:00
toplevel - > set_is_syntax_error ( * syntax_error_node ) ;
2020-06-23 14:40:41 +00:00
}
return toplevel ;
2020-06-17 13:35:06 +00:00
}
2021-01-11 09:34:59 +00: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 13:35:06 +00:00
RefPtr < AST : : Node > Parser : : parse_toplevel ( )
{
auto rule_start = push_start ( ) ;
2021-01-16 19:50:52 +00:00
SequenceParseResult result ;
NonnullRefPtrVector < AST : : Node > sequence ;
Vector < AST : : Position > positions ;
do {
result = parse_sequence ( ) ;
if ( result . entries . is_empty ( ) )
break ;
2021-06-12 11:24:45 +00:00
sequence . extend ( move ( result . entries ) ) ;
positions . extend ( move ( result . separator_positions ) ) ;
2021-01-16 19:50:52 +00:00
} while ( result . decision = = ShouldReadMoreSequences : : Yes ) ;
if ( sequence . is_empty ( ) )
return nullptr ;
2020-06-17 13:35:06 +00:00
2021-01-16 19:50:52 +00:00
return create < AST : : Execute > (
create < AST : : Sequence > ( move ( sequence ) , move ( positions ) ) ) ;
2020-06-17 13:35:06 +00:00
}
2021-01-16 19:50:52 +00:00
Parser : : SequenceParseResult Parser : : parse_sequence ( )
2020-06-17 13:35:06 +00:00
{
2021-01-16 19:50:52 +00:00
NonnullRefPtrVector < AST : : Node > left ;
2021-04-29 02:34:00 +00:00
auto read_terminators = [ & ] ( bool consider_tabs_and_spaces ) {
if ( m_heredoc_initiations . is_empty ( ) ) {
discard_terminators : ;
consume_while ( is_any_of ( consider_tabs_and_spaces ? " \t \n ; " : " \n ; " ) ) ;
} else {
for ( ; ; ) {
if ( consider_tabs_and_spaces & & ( peek ( ) = = ' \t ' | | peek ( ) = = ' ' ) ) {
consume ( ) ;
continue ;
}
if ( peek ( ) = = ' ; ' ) {
consume ( ) ;
continue ;
}
if ( peek ( ) = = ' \n ' ) {
auto rule_start = push_start ( ) ;
consume ( ) ;
if ( ! parse_heredoc_entries ( ) ) {
StringBuilder error_builder ;
error_builder . append ( " Expected to find heredoc entries for " ) ;
bool first = true ;
for ( auto & entry : m_heredoc_initiations ) {
if ( first )
error_builder . appendff ( " {} (at {}:{}) " , entry . end , entry . node - > position ( ) . start_line . line_column , entry . node - > position ( ) . start_line . line_number ) ;
else
error_builder . appendff ( " , {} (at {}:{}) " , entry . end , entry . node - > position ( ) . start_line . line_column , entry . node - > position ( ) . start_line . line_number ) ;
first = false ;
}
left . append ( create < AST : : SyntaxError > ( error_builder . build ( ) , true ) ) ;
// Just read the rest of the newlines
goto discard_terminators ;
}
continue ;
}
break ;
}
}
} ;
read_terminators ( true ) ;
2021-01-16 19:50:52 +00:00
2020-06-17 13:35:06 +00:00
auto rule_start = push_start ( ) ;
2021-01-16 19:50:52 +00:00
{
auto var_decls = parse_variable_decls ( ) ;
if ( var_decls )
left . append ( var_decls . release_nonnull ( ) ) ;
}
2020-05-24 18:30:46 +00:00
2020-09-28 10:57:20 +00:00
auto pos_before_seps = save_offset ( ) ;
2020-06-28 14:13:37 +00:00
switch ( peek ( ) ) {
2020-07-11 21:12:46 +00:00
case ' } ' :
2021-01-16 19:50:52 +00:00
return { move ( left ) , { } , ShouldReadMoreSequences : : No } ;
2021-04-29 02:34:00 +00:00
case ' \n ' :
read_terminators ( false ) ;
[[fallthrough]] ;
case ' ; ' : {
2021-01-16 19:50:52 +00:00
if ( left . is_empty ( ) )
2020-07-11 21:12:46 +00:00
break ;
2020-06-28 14:13:37 +00:00
consume_while ( is_any_of ( " \n ; " ) ) ;
2020-09-28 10:57:20 +00:00
auto pos_after_seps = save_offset ( ) ;
2021-01-16 19:50:52 +00:00
AST : : Position separator_position { pos_before_seps . offset , pos_after_seps . offset , pos_before_seps . line , pos_after_seps . line } ;
2020-09-28 10:57:20 +00:00
2021-01-16 19:50:52 +00:00
return { move ( left ) , { move ( separator_position ) } , ShouldReadMoreSequences : : Yes } ;
2020-07-11 21:12:46 +00:00
}
2020-06-28 14:13:37 +00:00
default :
break ;
}
2021-01-16 19:50:52 +00:00
auto first_entry = parse_function_decl ( ) ;
2020-09-13 11:24:33 +00:00
2021-01-16 19:50:52 +00:00
Vector < AST : : Position > separator_positions ;
2020-07-11 21:12:46 +00:00
2021-01-16 19:50:52 +00:00
if ( ! first_entry )
first_entry = parse_or_logical_sequence ( ) ;
2020-06-17 13:35:06 +00:00
2021-01-16 19:50:52 +00: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 13:35:06 +00:00
consume_while ( is_whitespace ) ;
2020-09-28 10:57:20 +00:00
pos_before_seps = save_offset ( ) ;
2020-06-17 13:35:06 +00:00
switch ( peek ( ) ) {
2021-04-29 02:34:00 +00:00
case ' \n ' :
read_terminators ( false ) ;
[[fallthrough]] ;
case ' ; ' : {
2020-06-28 14:13:37 +00:00
consume_while ( is_any_of ( " \n ; " ) ) ;
2020-09-28 10:57:20 +00:00
auto pos_after_seps = save_offset ( ) ;
2021-01-16 19:50:52 +00: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 10:57:20 +00:00
}
2020-06-17 13:35:06 +00:00
case ' & ' : {
consume ( ) ;
2020-09-28 10:57:20 +00:00
auto pos_after_seps = save_offset ( ) ;
2021-01-16 19:50:52 +00: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 13:35:06 +00:00
}
default :
2021-01-16 19:50:52 +00:00
return { move ( left ) , move ( separator_positions ) , ShouldReadMoreSequences : : No } ;
2020-06-17 13:35:06 +00:00
}
}
RefPtr < AST : : Node > Parser : : parse_variable_decls ( )
{
auto rule_start = push_start ( ) ;
consume_while ( is_whitespace ) ;
2020-09-28 10:57:20 +00:00
auto pos_before_name = save_offset ( ) ;
2020-06-17 13:35:06 +00:00
auto var_name = consume_while ( is_word_character ) ;
if ( var_name . is_empty ( ) )
return nullptr ;
if ( ! expect ( ' = ' ) ) {
2020-09-28 10:57:20 +00:00
restore_to ( pos_before_name . offset , pos_before_name . line ) ;
2020-06-17 13:35:06 +00:00
return nullptr ;
}
auto name_expr = create < AST : : BarewordLiteral > ( move ( var_name ) ) ;
2020-06-22 11:07:20 +00:00
auto start = push_start ( ) ;
2020-06-17 13:35:06 +00:00
auto expression = parse_expression ( ) ;
2020-06-22 11:07:20 +00:00
if ( ! expression | | expression - > is_syntax_error ( ) ) {
2020-09-28 10:57:20 +00:00
restore_to ( * start ) ;
2020-06-22 11:07:20 +00:00
if ( peek ( ) = = ' ( ' ) {
consume ( ) ;
auto command = parse_pipe_sequence ( ) ;
if ( ! command )
2020-09-28 10:57:20 +00:00
restore_to ( * start ) ;
2020-06-23 14:40:41 +00:00
else if ( ! expect ( ' ) ' ) )
2020-12-01 09:25:14 +00:00
command - > set_is_syntax_error ( * create < AST : : SyntaxError > ( " Expected a terminating close paren " , true ) ) ;
2020-06-22 11:07:20 +00:00
expression = command ;
}
}
2020-06-17 13:35:06 +00:00
if ( ! expression ) {
if ( is_whitespace ( peek ( ) ) ) {
auto string_start = push_start ( ) ;
expression = create < AST : : StringLiteral > ( " " ) ;
} else {
2020-09-28 10:57:20 +00:00
restore_to ( pos_before_name . offset , pos_before_name . line ) ;
2020-06-17 13:35:06 +00:00
return nullptr ;
}
}
Vector < AST : : VariableDeclarations : : Variable > variables ;
2020-08-07 07:41:04 +00:00
variables . append ( { move ( name_expr ) , expression . release_nonnull ( ) } ) ;
2020-06-17 13:35:06 +00: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 19:42:32 +00:00
VERIFY ( rest - > is_variable_decls ( ) ) ;
2020-06-17 13:35:06 +00:00
auto * rest_decl = static_cast < AST : : VariableDeclarations * > ( rest . ptr ( ) ) ;
2021-06-12 11:24:45 +00:00
variables . extend ( rest_decl - > variables ( ) ) ;
2020-06-17 13:35:06 +00:00
return create < AST : : VariableDeclarations > ( move ( variables ) ) ;
}
2020-09-13 11:24:33 +00:00
RefPtr < AST : : Node > Parser : : parse_function_decl ( )
{
auto rule_start = push_start ( ) ;
auto restore = [ & ] {
2020-09-28 10:57:20 +00:00
restore_to ( * rule_start ) ;
2020-09-13 11:24:33 +00:00
return nullptr ;
} ;
consume_while ( is_whitespace ) ;
2020-09-28 10:57:20 +00:00
auto pos_before_name = save_offset ( ) ;
2020-09-13 11:24:33 +00:00
auto function_name = consume_while ( is_word_character ) ;
2020-09-28 10:57:20 +00:00
auto pos_after_name = save_offset ( ) ;
2020-09-13 11:24:33 +00:00
if ( function_name . is_empty ( ) )
return restore ( ) ;
if ( ! expect ( ' ( ' ) )
return restore ( ) ;
2021-03-05 13:03:23 +00:00
Vector < AST : : NameWithPosition > arguments ;
2020-09-13 11:24:33 +00:00
for ( ; ; ) {
consume_while ( is_whitespace ) ;
if ( expect ( ' ) ' ) )
break ;
auto name_offset = m_offset ;
2020-09-28 10:57:20 +00:00
auto start_line = line ( ) ;
2020-09-13 11:24:33 +00: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 10:57:20 +00:00
arguments . append ( { arg_name , { name_offset , m_offset , start_line , line ( ) } } ) ;
2020-09-13 11:24:33 +00:00
}
2021-04-08 07:57:12 +00:00
consume_while ( is_any_of ( " \n \t " ) ) ;
2020-09-13 11:24:33 +00:00
{
RefPtr < AST : : Node > syntax_error ;
{
auto obrace_error_start = push_start ( ) ;
2020-12-01 09:25:14 +00:00
syntax_error = create < AST : : SyntaxError > ( " Expected an open brace '{' to start a function body " , true ) ;
2020-09-13 11:24:33 +00:00
}
if ( ! expect ( ' { ' ) ) {
return create < AST : : FunctionDeclaration > (
2021-03-05 13:03:23 +00:00
AST : : NameWithPosition {
2020-09-13 11:24:33 +00:00
move ( function_name ) ,
2020-09-28 10:57:20 +00:00
{ pos_before_name . offset , pos_after_name . offset , pos_before_name . line , pos_after_name . line } } ,
2020-09-13 11:24:33 +00:00
move ( arguments ) ,
move ( syntax_error ) ) ;
}
}
2020-12-10 14:55:13 +00:00
TemporaryChange controls { m_continuation_controls_allowed , false } ;
2020-09-13 11:24:33 +00:00
auto body = parse_toplevel ( ) ;
{
RefPtr < AST : : SyntaxError > syntax_error ;
{
auto cbrace_error_start = push_start ( ) ;
2020-12-01 09:25:14 +00:00
syntax_error = create < AST : : SyntaxError > ( " Expected a close brace '}' to end a function body " , true ) ;
2020-09-13 11:24:33 +00:00
}
if ( ! expect ( ' } ' ) ) {
if ( body )
body - > set_is_syntax_error ( * syntax_error ) ;
else
body = move ( syntax_error ) ;
return create < AST : : FunctionDeclaration > (
2021-03-05 13:03:23 +00:00
AST : : NameWithPosition {
2020-09-13 11:24:33 +00:00
move ( function_name ) ,
2020-09-28 10:57:20 +00:00
{ pos_before_name . offset , pos_after_name . offset , pos_before_name . line , pos_after_name . line } } ,
2020-09-13 11:24:33 +00:00
move ( arguments ) ,
move ( body ) ) ;
}
}
return create < AST : : FunctionDeclaration > (
2021-03-05 13:03:23 +00:00
AST : : NameWithPosition {
2020-09-13 11:24:33 +00:00
move ( function_name ) ,
2020-09-28 10:57:20 +00:00
{ pos_before_name . offset , pos_after_name . offset , pos_before_name . line , pos_after_name . line } } ,
2020-09-13 11:24:33 +00:00
move ( arguments ) ,
move ( body ) ) ;
}
2020-07-11 21:12:46 +00: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 10:57:20 +00:00
auto pos_before_or = save_offset ( ) ;
if ( ! expect ( " || " ) )
2020-07-11 21:12:46 +00:00
return and_sequence ;
2020-09-28 10:57:20 +00:00
auto pos_after_or = save_offset ( ) ;
2020-07-11 21:12:46 +00:00
auto right_and_sequence = parse_and_logical_sequence ( ) ;
if ( ! right_and_sequence )
2020-12-01 09:25:14 +00:00
right_and_sequence = create < AST : : SyntaxError > ( " Expected an expression after '||' " , true ) ;
2020-07-11 21:12:46 +00:00
2020-09-28 10:57:20 +00: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-11 21:12:46 +00: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 10:57:20 +00:00
auto pos_before_and = save_offset ( ) ;
if ( ! expect ( " && " ) )
2020-07-11 21:12:46 +00:00
return pipe_sequence ;
2020-09-28 10:57:20 +00:00
auto pos_after_end = save_offset ( ) ;
2020-07-11 21:12:46 +00:00
2020-07-16 16:12:12 +00:00
auto right_and_sequence = parse_and_logical_sequence ( ) ;
if ( ! right_and_sequence )
2020-12-01 09:25:14 +00:00
right_and_sequence = create < AST : : SyntaxError > ( " Expected an expression after '&&' " , true ) ;
2020-07-11 21:12:46 +00:00
2020-09-28 10:57:20 +00: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-11 21:12:46 +00:00
}
2020-06-17 13:35:06 +00:00
RefPtr < AST : : Node > Parser : : parse_pipe_sequence ( )
{
auto rule_start = push_start ( ) ;
2020-09-07 16:19:53 +00:00
auto left = parse_control_structure ( ) ;
if ( ! left ) {
if ( auto cmd = parse_command ( ) )
left = cmd ;
else
return nullptr ;
}
2020-06-17 13:35:06 +00:00
consume_while ( is_whitespace ) ;
if ( peek ( ) ! = ' | ' )
2020-09-07 16:19:53 +00:00
return left ;
2020-06-17 13:35:06 +00:00
2020-09-28 10:57:20 +00:00
auto before_pipe = save_offset ( ) ;
2020-06-17 13:35:06 +00:00
consume ( ) ;
if ( auto pipe_seq = parse_pipe_sequence ( ) ) {
2020-09-16 00:37:14 +00:00
return create < AST : : Pipe > ( left . release_nonnull ( ) , pipe_seq . release_nonnull ( ) ) ; // Pipe
2020-06-17 13:35:06 +00:00
}
2020-09-28 10:57:20 +00:00
restore_to ( before_pipe . offset , before_pipe . line ) ;
2020-09-07 16:19:53 +00:00
return left ;
2020-06-17 13:35:06 +00: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 00:37:14 +00:00
auto cast = create < AST : : CastToCommand > ( list_expr . release_nonnull ( ) ) ; // Cast List Command
2020-06-20 13:30:45 +00:00
auto next_command = parse_command ( ) ;
2020-06-17 13:35:06 +00:00
if ( ! next_command )
return cast ;
2020-09-16 00:37:14 +00:00
return create < AST : : Join > ( move ( cast ) , next_command . release_nonnull ( ) ) ; // Join List Command
2020-06-17 13:35:06 +00:00
}
auto command = parse_command ( ) ;
if ( ! command )
return redir ;
2020-09-16 00:37:14 +00:00
return create < AST : : Join > ( redir . release_nonnull ( ) , command . release_nonnull ( ) ) ; // Join Command Command
2020-06-17 13:35:06 +00:00
}
2020-07-11 21:12:46 +00:00
RefPtr < AST : : Node > Parser : : parse_control_structure ( )
{
auto rule_start = push_start ( ) ;
consume_while ( is_whitespace ) ;
2020-12-10 14:55:13 +00:00
if ( auto control = parse_continuation_control ( ) )
return control ;
2020-07-11 21:12:46 +00:00
if ( auto for_loop = parse_for_loop ( ) )
return for_loop ;
2020-12-10 14:55:13 +00:00
if ( auto loop = parse_loop_loop ( ) )
return loop ;
2020-08-11 07:35:46 +00:00
if ( auto if_expr = parse_if_expr ( ) )
return if_expr ;
2020-09-08 11:29:07 +00:00
if ( auto subshell = parse_subshell ( ) )
return subshell ;
2020-09-14 15:02:21 +00:00
if ( auto match = parse_match_expr ( ) )
return match ;
2020-07-11 21:12:46 +00:00
return nullptr ;
}
2020-12-10 14:55:13 +00: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-11 21:12:46 +00:00
RefPtr < AST : : Node > Parser : : parse_for_loop ( )
{
auto rule_start = push_start ( ) ;
2020-09-28 10:57:20 +00:00
if ( ! expect ( " for " ) )
2020-07-11 21:12:46 +00:00
return nullptr ;
if ( consume_while ( is_any_of ( " \t \n " ) ) . is_empty ( ) ) {
2020-09-28 10:57:20 +00:00
restore_to ( * rule_start ) ;
2020-07-11 21:12:46 +00:00
return nullptr ;
}
2021-03-05 14:55:09 +00: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-11 21:12:46 +00:00
consume_while ( is_whitespace ) ;
auto in_error_start = push_start ( ) ;
if ( ! expect ( " in " ) ) {
2020-12-01 09:25:14 +00:00
auto syntax_error = create < AST : : SyntaxError > ( " Expected 'in' after a variable name in a 'for' loop " , true ) ;
2021-03-05 14:55:09 +00:00
return create < AST : : ForLoop > ( move ( variable_name ) , move ( index_variable_name ) , move ( syntax_error ) , nullptr ) ; // ForLoop Var Iterated Block
2020-07-11 21:12:46 +00:00
}
2020-09-28 10:57:20 +00:00
in_start_position = AST : : Position { in_error_start - > offset , m_offset , in_error_start - > line , line ( ) } ;
2020-07-11 21:12:46 +00:00
}
consume_while ( is_whitespace ) ;
RefPtr < AST : : Node > iterated_expression ;
{
auto iter_error_start = push_start ( ) ;
iterated_expression = parse_expression ( ) ;
2020-12-10 14:55:13 +00:00
if ( ! iterated_expression )
iterated_expression = create < AST : : SyntaxError > ( " Expected an expression in 'for' loop " , true ) ;
2020-07-11 21:12:46 +00:00
}
consume_while ( is_any_of ( " \t \n " ) ) ;
{
auto obrace_error_start = push_start ( ) ;
if ( ! expect ( ' { ' ) ) {
2020-12-01 09:25:14 +00:00
auto syntax_error = create < AST : : SyntaxError > ( " Expected an open brace '{' to start a 'for' loop body " , true ) ;
2021-03-05 14:55:09 +00: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-11 21:12:46 +00:00
}
}
2020-12-10 14:55:13 +00:00
TemporaryChange controls { m_continuation_controls_allowed , true } ;
2020-07-11 21:12:46 +00:00
auto body = parse_toplevel ( ) ;
{
auto cbrace_error_start = push_start ( ) ;
if ( ! expect ( ' } ' ) ) {
auto error_start = push_start ( ) ;
2020-12-01 09:25:14 +00:00
auto syntax_error = create < AST : : SyntaxError > ( " Expected a close brace '}' to end a 'for' loop body " , true ) ;
2020-07-11 21:12:46 +00:00
if ( body )
body - > set_is_syntax_error ( * syntax_error ) ;
else
body = syntax_error ;
}
}
2021-03-05 14:55:09 +00: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 14:55:13 +00: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 14:55:09 +00:00
return create < AST : : ForLoop > ( AST : : NameWithPosition { } , AST : : NameWithPosition { } , nullptr , move ( syntax_error ) ) ; // ForLoop null null Block
2020-12-10 14:55:13 +00: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 14:55:09 +00:00
return create < AST : : ForLoop > ( AST : : NameWithPosition { } , AST : : NameWithPosition { } , nullptr , move ( body ) ) ; // ForLoop null null Block
2020-07-11 21:12:46 +00:00
}
2020-08-11 07:35:46 +00:00
RefPtr < AST : : Node > Parser : : parse_if_expr ( )
{
auto rule_start = push_start ( ) ;
2020-09-28 10:57:20 +00:00
if ( ! expect ( " if " ) )
2020-08-11 07:35:46 +00:00
return nullptr ;
if ( consume_while ( is_any_of ( " \t \n " ) ) . is_empty ( ) ) {
2020-09-28 10:57:20 +00:00
restore_to ( * rule_start ) ;
2020-08-11 07:35:46 +00:00
return nullptr ;
}
RefPtr < AST : : Node > condition ;
{
auto cond_error_start = push_start ( ) ;
condition = parse_or_logical_sequence ( ) ;
2020-09-16 00:37:14 +00:00
if ( ! condition )
2020-12-01 09:25:14 +00:00
condition = create < AST : : SyntaxError > ( " Expected a logical sequence after 'if' " , true ) ;
2020-08-11 07:35:46 +00:00
}
auto parse_braced_toplevel = [ & ] ( ) - > RefPtr < AST : : Node > {
2020-09-16 00:37:14 +00:00
RefPtr < AST : : Node > body ;
2020-08-11 07:35:46 +00:00
{
auto obrace_error_start = push_start ( ) ;
if ( ! expect ( ' { ' ) ) {
2020-12-01 09:25:14 +00:00
body = create < AST : : SyntaxError > ( " Expected an open brace '{' to start an 'if' true branch " , true ) ;
2020-08-11 07:35:46 +00:00
}
}
2020-09-16 00:37:14 +00:00
if ( ! body )
body = parse_toplevel ( ) ;
2020-08-11 07:35:46 +00:00
{
auto cbrace_error_start = push_start ( ) ;
if ( ! expect ( ' } ' ) ) {
auto error_start = push_start ( ) ;
2020-12-01 09:25:14 +00:00
RefPtr < AST : : SyntaxError > syntax_error = create < AST : : SyntaxError > ( " Expected a close brace '}' to end an 'if' true branch " , true ) ;
2020-08-11 07:35:46 +00:00
if ( body )
body - > set_is_syntax_error ( * syntax_error ) ;
else
body = syntax_error ;
}
}
return body ;
} ;
2021-01-17 19:03:59 +00:00
consume_while ( is_any_of ( " \t \n " ) ) ;
2020-08-11 07:35:46 +00:00
auto true_branch = parse_braced_toplevel ( ) ;
2021-01-17 19:03:59 +00:00
auto end_before_else = m_offset ;
auto line_before_else = line ( ) ;
consume_while ( is_any_of ( " \t \n " ) ) ;
2020-08-11 07:35:46 +00:00
Optional < AST : : Position > else_position ;
{
auto else_start = push_start ( ) ;
if ( expect ( " else " ) )
2020-09-28 10:57:20 +00:00
else_position = AST : : Position { else_start - > offset , m_offset , else_start - > line , line ( ) } ;
2021-01-17 19:03:59 +00:00
else
restore_to ( end_before_else , line_before_else ) ;
2020-08-11 07:35:46 +00:00
}
if ( else_position . has_value ( ) ) {
2021-01-17 19:03:59 +00:00
consume_while ( is_any_of ( " \t \n " ) ) ;
2020-08-11 07:35:46 +00:00
if ( peek ( ) = = ' { ' ) {
auto false_branch = parse_braced_toplevel ( ) ;
2020-09-16 00:37:14 +00: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 07:35:46 +00:00
}
auto else_if_branch = parse_if_expr ( ) ;
2020-09-16 00:37:14 +00: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 07:35:46 +00:00
}
2020-09-16 00:37:14 +00:00
return create < AST : : IfCond > ( else_position , condition . release_nonnull ( ) , move ( true_branch ) , nullptr ) ; // If expr true_branch
2020-08-11 07:35:46 +00:00
}
2020-09-08 11:29:07 +00: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 09:25:14 +00:00
RefPtr < AST : : SyntaxError > syntax_error = create < AST : : SyntaxError > ( " Expected a close brace '}' to end a subshell " , true ) ;
2020-09-08 11:29:07 +00:00
if ( body )
body - > set_is_syntax_error ( * syntax_error ) ;
else
body = syntax_error ;
}
}
return create < AST : : Subshell > ( move ( body ) ) ;
}
2020-09-14 15:02:21 +00: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 10:57:20 +00:00
restore_to ( * rule_start ) ;
2020-09-14 15:02:21 +00:00
return nullptr ;
}
auto match_expression = parse_expression ( ) ;
if ( ! match_expression ) {
return create < AST : : MatchExpr > (
2020-12-01 09:25:14 +00:00
create < AST : : SyntaxError > ( " Expected an expression after 'match' " , true ) ,
2020-09-14 15:02:21 +00: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 10:57:20 +00:00
auto as_line = line ( ) ;
2020-09-14 15:02:21 +00:00
if ( expect ( " as " ) ) {
2020-09-28 10:57:20 +00:00
as_position = AST : : Position { as_start , m_offset , as_line , line ( ) } ;
2020-09-14 15:02:21 +00:00
if ( consume_while ( is_any_of ( " \t \n " ) ) . is_empty ( ) ) {
auto node = create < AST : : MatchExpr > (
2020-09-16 00:37:14 +00:00
match_expression . release_nonnull ( ) ,
2020-09-14 15:02:21 +00:00
String { } , move ( as_position ) , Vector < AST : : MatchEntry > { } ) ;
2020-12-01 09:25:14 +00:00
node - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected whitespace after 'as' in 'match' " , true ) ) ;
2020-09-14 15:02:21 +00:00
return node ;
}
match_name = consume_while ( is_word_character ) ;
if ( match_name . is_empty ( ) ) {
auto node = create < AST : : MatchExpr > (
2020-09-16 00:37:14 +00:00
match_expression . release_nonnull ( ) ,
2020-09-14 15:02:21 +00:00
String { } , move ( as_position ) , Vector < AST : : MatchEntry > { } ) ;
2020-12-01 09:25:14 +00:00
node - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected an identifier after 'as' in 'match' " , true ) ) ;
2020-09-14 15:02:21 +00:00
return node ;
}
}
consume_while ( is_any_of ( " \t \n " ) ) ;
if ( ! expect ( ' { ' ) ) {
auto node = create < AST : : MatchExpr > (
2020-09-16 00:37:14 +00:00
match_expression . release_nonnull ( ) ,
2020-09-14 15:02:21 +00:00
move ( match_name ) , move ( as_position ) , Vector < AST : : MatchEntry > { } ) ;
2020-12-01 09:25:14 +00:00
node - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected an open brace '{' to start a 'match' entry list " , true ) ) ;
2020-09-14 15:02:21 +00: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 00:37:14 +00:00
match_expression . release_nonnull ( ) ,
2020-09-14 15:02:21 +00:00
move ( match_name ) , move ( as_position ) , move ( entries ) ) ;
2020-12-01 09:25:14 +00:00
node - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected a close brace '}' to end a 'match' entry list " , true ) ) ;
2020-09-14 15:02:21 +00:00
return node ;
}
2020-09-16 00:37:14 +00:00
return create < AST : : MatchExpr > ( match_expression . release_nonnull ( ) , move ( match_name ) , move ( as_position ) , move ( entries ) ) ;
2020-09-14 15:02:21 +00:00
}
AST : : MatchEntry Parser : : parse_match_entry ( )
{
auto rule_start = push_start ( ) ;
NonnullRefPtrVector < AST : : Node > patterns ;
Vector < AST : : Position > pipe_positions ;
2020-10-25 07:12:03 +00:00
Optional < Vector < String > > match_names ;
Optional < AST : : Position > match_as_position ;
2020-09-14 15:02:21 +00:00
auto pattern = parse_match_pattern ( ) ;
if ( ! pattern )
2020-12-01 09:25:14 +00:00
return { { } , { } , { } , { } , create < AST : : SyntaxError > ( " Expected a pattern in 'match' body " , true ) } ;
2020-09-14 15:02:21 +00:00
patterns . append ( pattern . release_nonnull ( ) ) ;
consume_while ( is_any_of ( " \t \n " ) ) ;
auto previous_pipe_start_position = m_offset ;
2020-09-28 10:57:20 +00:00
auto previous_pipe_start_line = line ( ) ;
2020-09-14 15:02:21 +00:00
RefPtr < AST : : SyntaxError > error ;
while ( expect ( ' | ' ) ) {
2020-09-28 10:57:20 +00:00
pipe_positions . append ( { previous_pipe_start_position , m_offset , previous_pipe_start_line , line ( ) } ) ;
2020-09-14 15:02:21 +00:00
consume_while ( is_any_of ( " \t \n " ) ) ;
auto pattern = parse_match_pattern ( ) ;
if ( ! pattern ) {
2020-12-01 09:25:14 +00:00
error = create < AST : : SyntaxError > ( " Expected a pattern to follow '|' in 'match' body " , true ) ;
2020-09-14 15:02:21 +00:00
break ;
}
consume_while ( is_any_of ( " \t \n " ) ) ;
patterns . append ( pattern . release_nonnull ( ) ) ;
2020-09-28 10:57:20 +00:00
previous_pipe_start_line = line ( ) ;
previous_pipe_start_position = m_offset ;
2020-09-14 15:02:21 +00:00
}
consume_while ( is_any_of ( " \t \n " ) ) ;
2020-10-25 07:12:03 +00: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 09:25:14 +00:00
error = create < AST : : SyntaxError > ( " Expected a close paren ')' to end the identifier list of pattern 'as' " , true ) ;
2020-10-25 07:12:03 +00:00
}
}
consume_while ( is_any_of ( " \t \n " ) ) ;
}
2020-09-14 15:02:21 +00:00
if ( ! expect ( ' { ' ) ) {
if ( ! error )
2020-12-01 09:25:14 +00:00
error = create < AST : : SyntaxError > ( " Expected an open brace '{' to start a match entry body " , true ) ;
2020-09-14 15:02:21 +00:00
}
auto body = parse_toplevel ( ) ;
if ( ! expect ( ' } ' ) ) {
if ( ! error )
2020-12-01 09:25:14 +00:00
error = create < AST : : SyntaxError > ( " Expected a close brace '}' to end a match entry body " , true ) ;
2020-09-14 15:02:21 +00:00
}
if ( body & & error )
body - > set_is_syntax_error ( * error ) ;
else if ( error )
body = error ;
2020-10-25 07:12:03 +00:00
return { move ( patterns ) , move ( match_names ) , move ( match_as_position ) , move ( pipe_positions ) , move ( body ) } ;
2020-09-14 15:02:21 +00:00
}
RefPtr < AST : : Node > Parser : : parse_match_pattern ( )
{
return parse_expression ( ) ;
}
2020-06-17 13:35:06 +00:00
RefPtr < AST : : Node > Parser : : parse_redirection ( )
{
auto rule_start = push_start ( ) ;
2021-04-29 02:34:00 +00:00
// heredoc entry
if ( next_is ( " <<- " ) | | next_is ( " <<~ " ) )
return nullptr ;
2020-06-17 13:35:06 +00:00
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 12:48:13 +00:00
pipe_fd = fd . value_or ( - 1 ) ;
2020-06-17 13:35:06 +00: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-06 23:12:08 +00:00
}
2020-12-01 09:25:14 +00:00
path = create < AST : : SyntaxError > ( " Expected a path after redirection " , true ) ;
2019-05-06 23:12:08 +00:00
}
2020-09-16 00:37:14 +00:00
return create < AST : : WriteAppendRedirection > ( pipe_fd , path . release_nonnull ( ) ) ; // Redirection WriteAppend
2020-06-17 13:35:06 +00: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-06 23:12:08 +00:00
}
2020-06-17 13:35:06 +00: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 09:05:18 +00:00
dest_pipe_fd = fd . value_or ( - 1 ) ;
2019-09-14 09:36:09 +00:00
}
2020-06-28 14:12:57 +00: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 13:35:06 +00: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-06 23:12:08 +00:00
}
2020-12-01 09:25:14 +00:00
path = create < AST : : SyntaxError > ( " Expected a path after redirection " , true ) ;
2020-06-17 13:35:06 +00:00
}
2020-09-16 00:37:14 +00:00
return create < AST : : WriteRedirection > ( pipe_fd , path . release_nonnull ( ) ) ; // Redirection Write
2020-06-17 13:35:06 +00: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-06 23:12:08 +00:00
}
2020-12-01 09:25:14 +00:00
path = create < AST : : SyntaxError > ( " Expected a path after redirection " , true ) ;
2020-06-17 13:35:06 +00:00
}
if ( mode = = Read )
2020-09-16 00:37:14 +00:00
return create < AST : : ReadRedirection > ( pipe_fd , path . release_nonnull ( ) ) ; // Redirection Read
2020-04-30 00:56:16 +00:00
2020-09-16 00:37:14 +00:00
return create < AST : : ReadWriteRedirection > ( pipe_fd , path . release_nonnull ( ) ) ; // Redirection ReadWrite
2020-06-17 13:35:06 +00:00
}
default :
2020-09-28 10:57:20 +00:00
restore_to ( * rule_start ) ;
2020-06-17 13:35:06 +00:00
return nullptr ;
}
}
2019-12-04 12:31:53 +00:00
2020-06-17 13:35:06 +00:00
RefPtr < AST : : Node > Parser : : parse_list_expression ( )
{
consume_while ( is_whitespace ) ;
2019-12-04 12:31:53 +00:00
2020-06-17 13:35:06 +00:00
auto rule_start = push_start ( ) ;
2020-09-16 00:37:14 +00:00
Vector < NonnullRefPtr < AST : : Node > > nodes ;
2019-12-04 12:31:53 +00:00
2020-07-11 21:11:24 +00:00
do {
auto expr = parse_expression ( ) ;
if ( ! expr )
break ;
2020-09-16 00:37:14 +00:00
nodes . append ( expr . release_nonnull ( ) ) ;
2020-07-11 21:11:24 +00:00
} while ( ! consume_while ( is_whitespace ) . is_empty ( ) ) ;
2019-12-04 12:31:53 +00:00
2020-07-11 21:11:24 +00:00
if ( nodes . is_empty ( ) )
return nullptr ;
2019-05-25 22:38:11 +00:00
2020-07-11 21:11:24 +00:00
return create < AST : : ListConcatenate > ( move ( nodes ) ) ; // Concatenate List
2020-06-17 13:35:06 +00:00
}
RefPtr < AST : : Node > Parser : : parse_expression ( )
{
auto rule_start = push_start ( ) ;
2020-11-30 14:02:48 +00: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 13:35:06 +00:00
auto starting_char = peek ( ) ;
2020-09-16 00:37:14 +00:00
auto read_concat = [ & ] ( auto & & expr ) - > NonnullRefPtr < AST : : Node > {
2020-06-20 13:30:45 +00:00
if ( is_whitespace ( peek ( ) ) )
2020-09-16 00:37:14 +00:00
return move ( expr ) ;
2020-06-20 13:30:45 +00:00
if ( auto next_expr = parse_expression ( ) )
2020-09-16 00:37:14 +00:00
return create < AST : : Juxtaposition > ( move ( expr ) , next_expr . release_nonnull ( ) ) ;
2020-06-20 13:30:45 +00:00
2020-09-16 00:37:14 +00:00
return move ( expr ) ;
2020-06-20 13:30:45 +00:00
} ;
2021-04-29 02:34:00 +00:00
// Heredocs are expressions, so allow them
if ( ! ( next_is ( " <<- " ) | | next_is ( " <<~ " ) ) ) {
if ( strchr ( " &|)} ;<> \n " , starting_char ) ! = nullptr )
return nullptr ;
}
2020-10-24 14:43:02 +00:00
2021-01-11 09:34:59 +00:00
if ( m_extra_chars_not_allowed_in_barewords . contains_slow ( starting_char ) )
2020-10-24 14:43:02 +00:00
return nullptr ;
if ( m_is_in_brace_expansion_spec & & next_is ( " .. " ) )
2020-06-17 13:35:06 +00: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 00:37:14 +00:00
return read_concat ( variable . release_nonnull ( ) ) ;
2020-06-17 13:35:06 +00:00
2021-03-05 13:03:23 +00:00
if ( auto immediate = parse_immediate_expression ( ) )
return read_concat ( immediate . release_nonnull ( ) ) ;
2020-06-17 13:35:06 +00:00
if ( auto inline_exec = parse_evaluate ( ) )
2020-09-16 00:37:14 +00:00
return read_concat ( inline_exec . release_nonnull ( ) ) ;
2020-06-17 13:35:06 +00:00
}
if ( starting_char = = ' # ' )
return parse_comment ( ) ;
if ( starting_char = = ' ( ' ) {
consume ( ) ;
auto list = parse_list_expression ( ) ;
2020-06-22 11:07:20 +00:00
if ( ! expect ( ' ) ' ) ) {
2020-09-28 10:57:20 +00:00
restore_to ( * rule_start ) ;
2020-06-22 11:07:20 +00:00
return nullptr ;
}
2020-06-20 13:30:45 +00:00
return read_concat ( create < AST : : CastToList > ( move ( list ) ) ) ; // Cast To List
2020-06-17 13:35:06 +00:00
}
2021-03-05 12:44:53 +00:00
if ( starting_char = = ' ! ' & & m_in_interactive_mode ) {
2021-01-11 09:34:59 +00:00
if ( auto designator = parse_history_designator ( ) )
return designator ;
}
2020-09-16 00:37:14 +00:00
if ( auto composite = parse_string_composite ( ) )
return read_concat ( composite . release_nonnull ( ) ) ;
return nullptr ;
2020-06-17 13:35:06 +00: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 00:37:14 +00:00
return create < AST : : Juxtaposition > ( string . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate String StringComposite
2020-06-17 13:35:06 +00:00
return string ;
}
if ( auto variable = parse_variable ( ) ) {
if ( auto next_part = parse_string_composite ( ) )
2020-09-16 00:37:14 +00:00
return create < AST : : Juxtaposition > ( variable . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate Variable StringComposite
2020-06-17 13:35:06 +00:00
return variable ;
}
if ( auto glob = parse_glob ( ) ) {
if ( auto next_part = parse_string_composite ( ) )
2020-09-16 00:37:14 +00:00
return create < AST : : Juxtaposition > ( glob . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate Glob StringComposite
2020-06-17 13:35:06 +00:00
return glob ;
}
2020-10-24 14:43:02 +00: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 13:35:06 +00:00
if ( auto bareword = parse_bareword ( ) ) {
if ( auto next_part = parse_string_composite ( ) )
2020-09-16 00:37:14 +00:00
return create < AST : : Juxtaposition > ( bareword . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate Bareword StringComposite
2020-06-17 13:35:06 +00:00
return bareword ;
}
if ( auto inline_command = parse_evaluate ( ) ) {
if ( auto next_part = parse_string_composite ( ) )
2020-09-16 00:37:14 +00:00
return create < AST : : Juxtaposition > ( inline_command . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate Execute StringComposite
2020-06-17 13:35:06 +00:00
return inline_command ;
}
2021-04-29 02:34:00 +00:00
if ( auto heredoc = parse_heredoc_initiation_record ( ) ) {
if ( auto next_part = parse_string_composite ( ) )
return create < AST : : Juxtaposition > ( heredoc . release_nonnull ( ) , next_part . release_nonnull ( ) ) ; // Concatenate Heredoc StringComposite
return heredoc ;
}
2020-06-17 13:35:06 +00:00
return nullptr ;
}
RefPtr < AST : : Node > Parser : : parse_string ( )
{
auto rule_start = push_start ( ) ;
if ( at_end ( ) )
return nullptr ;
if ( peek ( ) = = ' " ' ) {
consume ( ) ;
2021-08-11 21:40:26 +00:00
auto inner = parse_string_inner ( StringEndCondition : : DoubleQuote ) ;
2020-06-23 14:40:41 +00:00
if ( ! inner )
2020-12-01 09:25:14 +00:00
inner = create < AST : : SyntaxError > ( " Unexpected EOF in string " , true ) ;
2020-06-23 14:40:41 +00:00
if ( ! expect ( ' " ' ) ) {
inner = create < AST : : DoubleQuotedString > ( move ( inner ) ) ;
2020-12-01 09:25:14 +00:00
inner - > set_is_syntax_error ( * create < AST : : SyntaxError > ( " Expected a terminating double quote " , true ) ) ;
2020-06-23 14:40:41 +00:00
return inner ;
}
2020-06-17 13:35:06 +00:00
return create < AST : : DoubleQuotedString > ( move ( inner ) ) ; // Double Quoted String
}
if ( peek ( ) = = ' \' ' ) {
consume ( ) ;
auto text = consume_while ( is_not ( ' \' ' ) ) ;
2020-06-23 14:40:41 +00:00
bool is_error = false ;
2020-06-17 13:35:06 +00:00
if ( ! expect ( ' \' ' ) )
2020-06-23 14:40:41 +00:00
is_error = true ;
auto result = create < AST : : StringLiteral > ( move ( text ) ) ; // String Literal
if ( is_error )
2020-12-01 09:25:14 +00:00
result - > set_is_syntax_error ( * create < AST : : SyntaxError > ( " Expected a terminating single quote " , true ) ) ;
2021-03-17 15:30:02 +00:00
return result ;
2020-06-17 13:35:06 +00:00
}
return nullptr ;
}
2021-08-11 21:40:26 +00:00
RefPtr < AST : : Node > Parser : : parse_string_inner ( StringEndCondition condition )
2020-06-17 13:35:06 +00:00
{
auto rule_start = push_start ( ) ;
if ( at_end ( ) )
return nullptr ;
StringBuilder builder ;
2021-08-11 21:40:26 +00:00
while ( ! at_end ( ) ) {
if ( condition = = StringEndCondition : : DoubleQuote & & peek ( ) = = ' " ' ) {
break ;
}
2020-06-17 13:35:06 +00:00
if ( peek ( ) = = ' \\ ' ) {
consume ( ) ;
if ( at_end ( ) ) {
2019-05-06 23:12:08 +00:00
break ;
}
2020-06-17 13:35:06 +00:00
auto ch = consume ( ) ;
switch ( ch ) {
case ' \\ ' :
default :
builder . append ( ch ) ;
2019-05-06 23:12:08 +00:00
break ;
2020-06-17 13:35:06 +00: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-06 23:12:08 +00:00
}
2020-06-17 13:35:06 +00:00
builder . append ( to_byte ( first_nibble , second_nibble ) ) ;
2020-04-30 00:56:16 +00:00
break ;
}
2021-05-10 07:00:42 +00:00
case ' u ' : {
if ( m_input . length ( ) < = m_offset + 8 )
break ;
size_t counter = 8 ;
auto chars = consume_while ( [ & ] ( auto ) { return counter - - > 0 ; } ) ;
if ( auto number = AK : : StringUtils : : convert_to_uint_from_hex ( chars ) ; number . has_value ( ) )
builder . append ( Utf32View { & number . value ( ) , 1 } ) ;
else
builder . append ( chars ) ;
break ;
}
2020-06-17 13:35:06 +00:00
case ' a ' :
builder . append ( ' \a ' ) ;
2020-04-30 00:56:16 +00:00
break ;
2020-06-17 13:35:06 +00:00
case ' b ' :
builder . append ( ' \b ' ) ;
2019-05-06 23:12:08 +00:00
break ;
2020-06-17 13:35:06 +00:00
case ' e ' :
builder . append ( ' \x1b ' ) ;
2019-05-06 23:12:08 +00:00
break ;
2020-06-17 13:35:06 +00:00
case ' f ' :
builder . append ( ' \f ' ) ;
2019-05-06 23:12:08 +00:00
break ;
2020-06-17 13:35:06 +00:00
case ' r ' :
builder . append ( ' \r ' ) ;
break ;
case ' n ' :
builder . append ( ' \n ' ) ;
2019-05-06 23:12:08 +00:00
break ;
2021-05-10 07:16:21 +00:00
case ' t ' :
builder . append ( ' \t ' ) ;
break ;
2019-05-06 23:12:08 +00:00
}
2020-06-17 13:35:06 +00:00
continue ;
}
if ( peek ( ) = = ' $ ' ) {
auto string_literal = create < AST : : StringLiteral > ( builder . to_string ( ) ) ; // String Literal
2021-03-05 13:03:23 +00:00
auto read_concat = [ & ] ( auto & & node ) {
2020-06-17 13:35:06 +00:00
auto inner = create < AST : : StringPartCompose > (
move ( string_literal ) ,
2021-03-05 13:03:23 +00:00
move ( node ) ) ; // Compose String Node
2020-06-17 13:35:06 +00:00
2021-08-11 21:40:26 +00:00
if ( auto string = parse_string_inner ( condition ) ) {
2020-09-16 00:37:14 +00:00
return create < AST : : StringPartCompose > ( move ( inner ) , string . release_nonnull ( ) ) ; // Compose Composition Composition
2019-09-14 09:36:09 +00:00
}
2020-06-17 13:35:06 +00:00
return inner ;
2021-03-05 13:03:23 +00:00
} ;
2020-06-17 13:35:06 +00:00
2021-03-05 13:03:23 +00:00
if ( auto variable = parse_variable ( ) )
return read_concat ( variable . release_nonnull ( ) ) ;
2020-06-17 13:35:06 +00:00
2021-03-05 13:03:23 +00:00
if ( auto immediate = parse_immediate_expression ( ) )
return read_concat ( immediate . release_nonnull ( ) ) ;
2020-06-17 13:35:06 +00:00
2021-03-05 13:03:23 +00:00
if ( auto evaluate = parse_evaluate ( ) )
return read_concat ( evaluate . release_nonnull ( ) ) ;
2020-06-17 13:35:06 +00:00
}
builder . append ( consume ( ) ) ;
}
return create < AST : : StringLiteral > ( builder . to_string ( ) ) ; // String Literal
}
RefPtr < AST : : Node > Parser : : parse_variable ( )
2021-03-12 23:40:18 +00:00
{
auto rule_start = push_start ( ) ;
auto ref = parse_variable_ref ( ) ;
if ( ! ref )
return nullptr ;
auto variable = static_ptr_cast < AST : : VariableNode > ( ref ) ;
if ( auto slice = parse_slice ( ) )
variable - > set_slice ( slice . release_nonnull ( ) ) ;
return variable ;
}
RefPtr < AST : : Node > Parser : : parse_variable_ref ( )
2020-06-17 13:35:06 +00:00
{
auto rule_start = push_start ( ) ;
if ( at_end ( ) )
return nullptr ;
if ( peek ( ) ! = ' $ ' )
return nullptr ;
consume ( ) ;
switch ( peek ( ) ) {
case ' $ ' :
case ' ? ' :
2020-08-04 04:57:25 +00:00
case ' * ' :
case ' # ' :
2020-06-17 13:35:06 +00:00
return create < AST : : SpecialVariable > ( consume ( ) ) ; // Variable Special
default :
break ;
}
auto name = consume_while ( is_word_character ) ;
if ( name . length ( ) = = 0 ) {
2020-09-28 10:57:20 +00:00
restore_to ( rule_start - > offset , rule_start - > line ) ;
2020-06-17 13:35:06 +00:00
return nullptr ;
2019-05-06 23:12:08 +00:00
}
2020-04-30 00:56:16 +00:00
2020-06-17 13:35:06 +00:00
return create < AST : : SimpleVariable > ( move ( name ) ) ; // Variable Simple
}
2021-03-12 23:40:18 +00:00
RefPtr < AST : : Node > Parser : : parse_slice ( )
{
auto rule_start = push_start ( ) ;
if ( ! next_is ( " [ " ) )
return nullptr ;
consume ( ) ; // [
ScopedValueRollback chars_change { m_extra_chars_not_allowed_in_barewords } ;
m_extra_chars_not_allowed_in_barewords . append ( ' ] ' ) ;
auto spec = parse_brace_expansion_spec ( ) ;
RefPtr < AST : : SyntaxError > error ;
if ( peek ( ) ! = ' ] ' )
error = create < AST : : SyntaxError > ( " Expected a close bracket ']' to end a variable slice " ) ;
else
consume ( ) ;
if ( ! spec ) {
if ( error )
spec = move ( error ) ;
else
spec = create < AST : : SyntaxError > ( " Expected either a range, or a comma-seprated list of selectors " ) ;
}
auto node = create < AST : : Slice > ( spec . release_nonnull ( ) ) ;
if ( error )
node - > set_is_syntax_error ( * error ) ;
return node ;
}
2020-06-17 13:35:06 +00:00
RefPtr < AST : : Node > Parser : : parse_evaluate ( )
{
auto rule_start = push_start ( ) ;
if ( at_end ( ) )
return nullptr ;
if ( peek ( ) ! = ' $ ' )
return nullptr ;
consume ( ) ;
2020-06-21 20:00:14 +00:00
if ( peek ( ) = = ' ( ' ) {
consume ( ) ;
auto inner = parse_pipe_sequence ( ) ;
2020-06-23 14:40:41 +00:00
if ( ! inner )
2020-12-01 09:25:14 +00:00
inner = create < AST : : SyntaxError > ( " Unexpected EOF in list " , true ) ;
2020-06-23 14:40:41 +00:00
if ( ! expect ( ' ) ' ) )
2020-12-01 09:25:14 +00:00
inner - > set_is_syntax_error ( * create < AST : : SyntaxError > ( " Expected a terminating close paren " , true ) ) ;
2020-09-16 00:37:14 +00:00
return create < AST : : Execute > ( inner . release_nonnull ( ) , true ) ;
2020-06-21 20:00:14 +00:00
}
2020-06-17 13:35:06 +00:00
auto inner = parse_expression ( ) ;
if ( ! inner ) {
2020-12-01 09:25:14 +00:00
inner = create < AST : : SyntaxError > ( " Expected a command " , true ) ;
2020-06-17 13:35:06 +00:00
} else {
if ( inner - > is_list ( ) ) {
2020-09-16 00:37:14 +00:00
auto execute_inner = create < AST : : Execute > ( inner . release_nonnull ( ) , true ) ;
inner = move ( execute_inner ) ;
2020-05-10 06:05:23 +00:00
} else {
2020-09-16 00:37:14 +00:00
auto dyn_inner = create < AST : : DynamicEvaluate > ( inner . release_nonnull ( ) ) ;
inner = move ( dyn_inner ) ;
2020-05-10 06:05:23 +00:00
}
2020-04-30 00:56:16 +00:00
}
2020-06-17 13:35:06 +00:00
return inner ;
}
2019-05-06 23:12:08 +00:00
2021-03-05 13:03:23 +00: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 09:34:59 +00:00
RefPtr < AST : : Node > Parser : : parse_history_designator ( )
{
auto rule_start = push_start ( ) ;
2021-02-23 19:42:32 +00:00
VERIFY ( peek ( ) = = ' ! ' ) ;
2021-01-11 09:34:59 +00:00
consume ( ) ;
// Event selector
AST : : HistorySelector selector ;
2021-02-06 23:12:57 +00:00
RefPtr < AST : : SyntaxError > syntax_error ;
2021-01-11 09:34:59 +00: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 16:50:05 +00: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 09:34:59 +00:00
} ;
2021-07-30 10:55:57 +00:00
bool is_word_selector = false ;
2021-01-11 09:34:59 +00:00
switch ( peek ( ) ) {
2021-07-30 10:55:57 +00:00
case ' : ' :
consume ( ) ;
[[fallthrough]] ;
case ' ^ ' :
case ' $ ' :
case ' * ' :
is_word_selector = true ;
break ;
2021-01-11 09:34:59 +00:00
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 : {
2021-07-30 10:55:57 +00:00
TemporaryChange chars_change { m_extra_chars_not_allowed_in_barewords , { ' : ' , ' ^ ' , ' $ ' , ' * ' } } ;
2021-01-11 09:34:59 +00:00
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-06 23:12:57 +00:00
selector . event . text_position = bareword - > position ( ) ;
2021-01-11 09:34:59 +00:00
auto it = selector . event . text . begin ( ) ;
bool is_negative = false ;
if ( * it = = ' - ' ) {
+ + it ;
is_negative = true ;
}
2021-02-25 20:10:47 +00:00
if ( it ! = selector . event . text . end ( ) & & all_of ( it , selector . event . text . end ( ) , is_digit ) ) {
2021-01-11 09:34:59 +00:00
if ( is_negative )
selector . event . kind = AST : : HistorySelector : : EventKind : : IndexFromEnd ;
else
selector . event . kind = AST : : HistorySelector : : EventKind : : IndexFromStart ;
2021-07-30 10:55:57 +00:00
auto number = abs ( selector . event . text . to_int ( ) . value_or ( 0 ) ) ;
if ( number ! = 0 )
selector . event . index = number - 1 ;
2021-02-06 23:12:57 +00:00
else
syntax_error = create < AST : : SyntaxError > ( " History entry index value invalid or out of range " ) ;
2021-01-11 09:34:59 +00:00
}
2021-07-30 10:55:57 +00:00
if ( " :^$* " sv . contains ( peek ( ) ) ) {
is_word_selector = true ;
if ( peek ( ) = = ' : ' )
consume ( ) ;
}
2021-01-11 09:34:59 +00:00
}
}
2021-07-30 10:55:57 +00:00
if ( ! is_word_selector ) {
2021-02-06 23:12:57 +00:00
auto node = create < AST : : HistoryEvent > ( move ( selector ) ) ;
if ( syntax_error )
node - > set_is_syntax_error ( * syntax_error ) ;
return node ;
}
2021-01-11 09:34:59 +00:00
// Word selectors
auto parse_word_selector = [ & ] ( ) - > Optional < AST : : HistorySelector : : WordSelector > {
auto c = peek ( ) ;
2021-07-30 10:55:57 +00:00
AST : : HistorySelector : : WordSelectorKind word_selector_kind ;
ssize_t offset = - 1 ;
2021-01-11 09:34:59 +00:00
if ( isdigit ( c ) ) {
auto num = consume_while ( is_digit ) ;
auto value = num . to_uint ( ) ;
2021-07-30 10:55:57 +00:00
if ( ! value . has_value ( ) )
return { } ;
word_selector_kind = AST : : HistorySelector : : WordSelectorKind : : Index ;
offset = value . value ( ) ;
} else if ( c = = ' ^ ' ) {
2021-01-11 09:34:59 +00:00
consume ( ) ;
2021-07-30 10:55:57 +00:00
word_selector_kind = AST : : HistorySelector : : WordSelectorKind : : Index ;
offset = 1 ;
} else if ( c = = ' $ ' ) {
2021-01-11 09:34:59 +00:00
consume ( ) ;
2021-07-30 10:55:57 +00:00
word_selector_kind = AST : : HistorySelector : : WordSelectorKind : : Last ;
offset = 0 ;
2021-01-11 09:34:59 +00:00
}
2021-07-30 10:55:57 +00:00
if ( offset = = - 1 )
return { } ;
return AST : : HistorySelector : : WordSelector {
word_selector_kind ,
static_cast < size_t > ( offset ) ,
{ m_rule_start_offsets . last ( ) , m_offset , m_rule_start_lines . last ( ) , line ( ) } ,
syntax_error
} ;
2021-01-11 09:34:59 +00:00
} ;
2021-07-30 10:55:57 +00:00
auto make_word_selector = [ & ] ( AST : : HistorySelector : : WordSelectorKind word_selector_kind , size_t offset ) {
return AST : : HistorySelector : : WordSelector {
word_selector_kind ,
offset ,
{ m_rule_start_offsets . last ( ) , m_offset , m_rule_start_lines . last ( ) , line ( ) } ,
syntax_error
} ;
} ;
auto first_char = peek ( ) ;
if ( ! ( is_digit ( first_char ) | | " ^$-* " sv . contains ( first_char ) ) ) {
2021-02-06 23:12:57 +00:00
if ( ! syntax_error )
syntax_error = create < AST : : SyntaxError > ( " Expected a word selector after ':' in a history event designator " , true ) ;
2021-07-30 10:55:57 +00:00
} else if ( first_char = = ' * ' ) {
2021-01-11 09:34:59 +00:00
consume ( ) ;
2021-07-30 10:55:57 +00:00
selector . word_selector_range . start = make_word_selector ( AST : : HistorySelector : : WordSelectorKind : : Index , 1 ) ;
selector . word_selector_range . end = make_word_selector ( AST : : HistorySelector : : WordSelectorKind : : Last , 0 ) ;
} else if ( first_char = = ' - ' ) {
consume ( ) ;
selector . word_selector_range . start = make_word_selector ( AST : : HistorySelector : : WordSelectorKind : : Index , 0 ) ;
auto last_selector = parse_word_selector ( ) ;
if ( ! last_selector . has_value ( ) )
selector . word_selector_range . end = make_word_selector ( AST : : HistorySelector : : WordSelectorKind : : Last , 1 ) ;
else
selector . word_selector_range . end = last_selector . release_value ( ) ;
2021-01-11 09:34:59 +00:00
} else {
2021-07-30 10:55:57 +00:00
auto first_selector = parse_word_selector ( ) ;
// peek() should be a digit, ^, or $ here, so this should always have value.
VERIFY ( first_selector . has_value ( ) ) ;
selector . word_selector_range . start = first_selector . release_value ( ) ;
if ( peek ( ) = = ' - ' ) {
consume ( ) ;
auto last_selector = parse_word_selector ( ) ;
if ( last_selector . has_value ( ) ) {
selector . word_selector_range . end = last_selector . release_value ( ) ;
} else {
selector . word_selector_range . end = make_word_selector ( AST : : HistorySelector : : WordSelectorKind : : Last , 1 ) ;
}
} else if ( peek ( ) = = ' * ' ) {
consume ( ) ;
selector . word_selector_range . end = make_word_selector ( AST : : HistorySelector : : WordSelectorKind : : Last , 0 ) ;
} else {
selector . word_selector_range . end . clear ( ) ;
}
2021-01-11 09:34:59 +00:00
}
2021-02-06 23:12:57 +00:00
auto node = create < AST : : HistoryEvent > ( move ( selector ) ) ;
if ( syntax_error )
node - > set_is_syntax_error ( * syntax_error ) ;
return node ;
2021-01-11 09:34:59 +00:00
}
2020-06-17 13:35:06 +00: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 14:43:02 +00:00
auto is_acceptable_bareword_character = [ & ] ( char c ) {
return strchr ( " \\ \" '*$&#|() { } ? ; < > \ n " , c) == nullptr
2021-01-11 09:34:59 +00:00
& & ! m_extra_chars_not_allowed_in_barewords . contains_slow ( c ) ;
2020-06-17 13:35:06 +00: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-06 23:12:08 +00:00
}
2020-06-17 13:35:06 +00:00
builder . append ( ch ) ;
continue ;
2019-05-06 23:12:08 +00:00
}
2020-06-17 13:35:06 +00:00
2020-10-24 14:43:02 +00:00
if ( m_is_in_brace_expansion_spec & & next_is ( " .. " ) ) {
// Don't eat '..' in a brace expansion spec.
break ;
}
2020-06-17 13:35:06 +00:00
if ( is_acceptable_bareword_character ( ch ) ) {
builder . append ( consume ( ) ) ;
continue ;
}
break ;
}
if ( builder . is_empty ( ) )
return nullptr ;
2020-06-23 14:40:41 +00:00
auto current_end = m_offset ;
2020-09-28 10:57:20 +00:00
auto current_line = line ( ) ;
2020-06-17 13:35:06 +00:00
auto string = builder . to_string ( ) ;
if ( string . starts_with ( ' ~ ' ) ) {
String username ;
2020-06-23 14:40:41 +00:00
RefPtr < AST : : Node > tilde , text ;
2021-05-24 09:50:46 +00:00
auto first_slash_index = string . find ( ' / ' ) ;
2020-06-17 13:35:06 +00:00
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 14:40:41 +00:00
// Synthesize a Tilde Node with the correct positioning information.
{
2020-09-28 10:57:20 +00:00
restore_to ( rule_start - > offset , rule_start - > line ) ;
auto ch = consume ( ) ;
2021-02-23 19:42:32 +00:00
VERIFY ( ch = = ' ~ ' ) ;
2021-03-15 07:55:20 +00:00
auto username_length = username . length ( ) ;
2020-06-23 14:40:41 +00:00
tilde = create < AST : : Tilde > ( move ( username ) ) ;
2021-03-15 07:55:20 +00:00
// Consume the username (if any)
for ( size_t i = 0 ; i < username_length ; + + i )
consume ( ) ;
2020-06-23 14:40:41 +00:00
}
2020-06-17 13:35:06 +00:00
if ( string . is_empty ( ) )
return tilde ;
2020-06-23 14:40:41 +00:00
// Synthesize a BarewordLiteral Node with the correct positioning information.
{
auto text_start = push_start ( ) ;
2020-09-28 10:57:20 +00:00
restore_to ( current_end , current_line ) ;
2020-06-23 14:40:41 +00:00
text = create < AST : : BarewordLiteral > ( move ( string ) ) ;
}
2020-10-02 21:14:37 +00:00
return create < AST : : Juxtaposition > ( tilde . release_nonnull ( ) , text . release_nonnull ( ) ) ; // Juxtaposition Variable Bareword
2019-05-06 23:12:08 +00:00
}
2020-06-17 13:35:06 +00:00
if ( string . starts_with ( " \\ ~ " ) ) {
// Un-escape the tilde, but only at the start (where it would be an expansion)
2020-06-23 14:40:41 +00:00
string = string . substring ( 1 , string . length ( ) - 1 ) ;
2020-06-17 13:35:06 +00:00
}
2020-06-23 14:40:41 +00:00
return create < AST : : BarewordLiteral > ( move ( string ) ) ; // Bareword Literal
2020-06-17 13:35:06 +00: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 10:57:20 +00:00
auto saved_offset = save_offset ( ) ;
2020-06-17 13:35:06 +00:00
consume ( ) ;
StringBuilder textbuilder ;
if ( bareword_part ) {
StringView text ;
2020-06-23 14:40:41 +00:00
if ( bareword_part - > is_bareword ( ) ) {
2020-06-17 13:35:06 +00:00
auto bareword = static_cast < AST : : BarewordLiteral * > ( bareword_part . ptr ( ) ) ;
text = bareword - > text ( ) ;
} else {
2020-06-23 14:40:41 +00:00
// FIXME: Allow composition of tilde+bareword with globs: '~/foo/bar/baz*'
2020-09-28 10:57:20 +00:00
restore_to ( saved_offset . offset , saved_offset . line ) ;
2021-04-21 20:16:14 +00:00
bareword_part - > set_is_syntax_error ( * create < AST : : SyntaxError > ( String : : formatted ( " Unexpected {} inside a glob " , bareword_part - > class_name ( ) ) ) ) ;
2020-06-23 14:40:41 +00:00
return bareword_part ;
2020-06-17 13:35:06 +00:00
}
textbuilder . append ( text ) ;
}
textbuilder . append ( ch ) ;
auto glob_after = parse_glob ( ) ;
if ( glob_after ) {
if ( glob_after - > is_glob ( ) ) {
2020-12-03 09:03:06 +00:00
auto glob = static_cast < AST : : Glob * > ( glob_after . ptr ( ) ) ;
2020-06-17 13:35:06 +00: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 12:48:13 +00: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 13:35:06 +00:00
} else {
2020-11-29 12:48:13 +00:00
return create < AST : : SyntaxError > ( String : : formatted ( " Invalid node '{}' in glob position, escape shell special characters " , glob_after - > class_name ( ) ) ) ;
2020-06-17 13:35:06 +00:00
}
}
return create < AST : : Glob > ( textbuilder . to_string ( ) ) ; // Glob
}
return bareword_part ;
}
2020-10-24 14:43:02 +00: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 09:25:14 +00:00
spec - > set_is_syntax_error ( create < AST : : SyntaxError > ( " Expected a close brace '}' to end a brace expansion " , true ) ) ;
2020-10-24 14:43:02 +00: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-03-12 23:40:18 +00:00
ScopedValueRollback chars_change { m_extra_chars_not_allowed_in_barewords } ;
m_extra_chars_not_allowed_in_barewords . append ( ' , ' ) ;
2021-01-11 09:34:59 +00:00
2020-10-24 14:43:02 +00: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 09:25:14 +00: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 14:43:02 +00: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 ) ) ;
}
2021-04-29 02:34:00 +00:00
RefPtr < AST : : Node > Parser : : parse_heredoc_initiation_record ( )
{
if ( ! next_is ( " << " ) )
return nullptr ;
auto rule_start = push_start ( ) ;
// '<' '<'
consume ( ) ;
consume ( ) ;
HeredocInitiationRecord record ;
record . end = " <error> " ;
RefPtr < AST : : SyntaxError > syntax_error_node ;
// '-' | '~'
switch ( peek ( ) ) {
case ' - ' :
record . deindent = false ;
consume ( ) ;
break ;
case ' ~ ' :
record . deindent = true ;
consume ( ) ;
break ;
default :
restore_to ( * rule_start ) ;
return nullptr ;
}
// StringLiteral | bareword
if ( auto bareword = parse_bareword ( ) ) {
2021-05-01 06:33:18 +00:00
if ( ! bareword - > is_bareword ( ) ) {
syntax_error_node = create < AST : : SyntaxError > ( String : : formatted ( " Expected a bareword or a quoted string, not {} " , bareword - > class_name ( ) ) ) ;
} else {
if ( bareword - > is_syntax_error ( ) )
syntax_error_node = bareword - > syntax_error_node ( ) ;
else
record . end = static_cast < AST : : BarewordLiteral * > ( bareword . ptr ( ) ) - > text ( ) ;
}
2021-04-29 02:34:00 +00:00
record . interpolate = true ;
} else if ( peek ( ) = = ' \' ' ) {
consume ( ) ;
auto text = consume_while ( is_not ( ' \' ' ) ) ;
bool is_error = false ;
if ( ! expect ( ' \' ' ) )
is_error = true ;
if ( is_error )
syntax_error_node = create < AST : : SyntaxError > ( " Expected a terminating single quote " , true ) ;
record . end = text ;
record . interpolate = false ;
} else {
syntax_error_node = create < AST : : SyntaxError > ( " Expected a bareword or a single-quoted string literal for heredoc end key " , true ) ;
}
auto node = create < AST : : Heredoc > ( record . end , record . interpolate , record . deindent ) ;
if ( syntax_error_node )
node - > set_is_syntax_error ( * syntax_error_node ) ;
else
node - > set_is_syntax_error ( * create < AST : : SyntaxError > ( String : : formatted ( " Expected heredoc contents for heredoc with end key '{}' " , node - > end ( ) ) , true ) ) ;
record . node = node ;
m_heredoc_initiations . append ( move ( record ) ) ;
return node ;
}
bool Parser : : parse_heredoc_entries ( )
{
2021-05-01 08:14:09 +00:00
auto heredocs = move ( m_heredoc_initiations ) ;
m_heredoc_initiations . clear ( ) ;
2021-04-29 02:34:00 +00:00
// Try to parse heredoc entries, as reverse recorded in the initiation records
2021-05-01 08:14:09 +00:00
for ( auto & record : heredocs ) {
2021-04-29 02:34:00 +00:00
auto rule_start = push_start ( ) ;
2021-05-01 06:33:18 +00:00
if ( m_rule_start_offsets . size ( ) > max_allowed_nested_rule_depth ) {
record . node - > set_is_syntax_error ( * create < AST : : SyntaxError > ( String : : formatted ( " Expression nested too deep (max allowed is {}) " , max_allowed_nested_rule_depth ) ) ) ;
continue ;
}
2021-04-29 02:34:00 +00:00
bool found_key = false ;
if ( ! record . interpolate ) {
// Since no interpolation is allowed, just read lines until we hit the key
Optional < Offset > last_line_offset ;
for ( ; ; ) {
if ( at_end ( ) )
break ;
if ( peek ( ) = = ' \n ' )
consume ( ) ;
last_line_offset = current_position ( ) ;
auto line = consume_while ( is_not ( ' \n ' ) ) ;
if ( peek ( ) = = ' \n ' )
consume ( ) ;
if ( line . trim_whitespace ( ) = = record . end ) {
found_key = true ;
break ;
}
}
if ( ! last_line_offset . has_value ( ) )
last_line_offset = current_position ( ) ;
// Now just wrap it in a StringLiteral and set it as the node's contents
auto node = create < AST : : StringLiteral > ( m_input . substring_view ( rule_start - > offset , last_line_offset - > offset - rule_start - > offset ) ) ;
if ( ! found_key )
node - > set_is_syntax_error ( * create < AST : : SyntaxError > ( String : : formatted ( " Expected to find the heredoc key '{}', but found Eof " , record . end ) , true ) ) ;
record . node - > set_contents ( move ( node ) ) ;
} else {
// Interpolation is allowed, so we're going to read doublequoted string innards
// until we find a line that contains the key
auto end_condition = move ( m_end_condition ) ;
found_key = false ;
2021-05-19 16:38:23 +00:00
set_end_condition ( make < Function < bool ( ) > > ( [ this , end = record . end , & found_key ] {
2021-04-29 02:34:00 +00:00
if ( found_key )
return true ;
auto offset = current_position ( ) ;
auto cond = move ( m_end_condition ) ;
ScopeGuard guard {
[ & ] {
m_end_condition = move ( cond ) ;
}
} ;
if ( peek ( ) = = ' \n ' ) {
consume ( ) ;
auto line = consume_while ( is_not ( ' \n ' ) ) ;
if ( peek ( ) = = ' \n ' )
consume ( ) ;
if ( line . trim_whitespace ( ) = = end ) {
restore_to ( offset . offset , offset . line ) ;
found_key = true ;
return true ;
}
}
restore_to ( offset . offset , offset . line ) ;
return false ;
2021-05-19 16:38:23 +00:00
} ) ) ;
2021-04-29 02:34:00 +00:00
2021-08-11 21:40:26 +00:00
auto expr = parse_string_inner ( StringEndCondition : : Heredoc ) ;
2021-04-29 02:34:00 +00:00
set_end_condition ( move ( end_condition ) ) ;
if ( found_key ) {
auto offset = current_position ( ) ;
if ( peek ( ) = = ' \n ' )
consume ( ) ;
auto line = consume_while ( is_not ( ' \n ' ) ) ;
if ( peek ( ) = = ' \n ' )
consume ( ) ;
if ( line . trim_whitespace ( ) ! = record . end )
restore_to ( offset . offset , offset . line ) ;
}
if ( ! expr & & found_key ) {
expr = create < AST : : StringLiteral > ( " " ) ;
} else if ( ! expr ) {
expr = create < AST : : SyntaxError > ( String : : formatted ( " Expected to find a valid string inside a heredoc (with end key '{}') " , record . end ) , true ) ;
} else if ( ! found_key ) {
expr - > set_is_syntax_error ( * create < AST : : SyntaxError > ( String : : formatted ( " Expected to find the heredoc key '{}' " , record . end ) , true ) ) ;
}
record . node - > set_contents ( create < AST : : DoubleQuotedString > ( move ( expr ) ) ) ;
}
}
return true ;
}
2020-06-17 13:35:06 +00:00
StringView Parser : : consume_while ( Function < bool ( char ) > condition )
{
2020-11-29 12:48:13 +00:00
if ( at_end ( ) )
return { } ;
2020-06-17 13:35:06 +00: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-06 23:12:08 +00:00
}
2020-10-01 14:43:01 +00:00
2020-10-24 14:43:02 +00:00
bool Parser : : next_is ( const StringView & next )
{
2021-04-29 02:30:52 +00:00
auto start = current_position ( ) ;
2020-10-24 14:43:02 +00:00
auto res = expect ( next ) ;
2021-04-29 02:30:52 +00:00
restore_to ( start . offset , start . line ) ;
2020-10-24 14:43:02 +00:00
return res ;
}
2020-10-01 14:43:01 +00:00
}