2021-03-05 13:03:23 +00:00
/*
2021-04-28 20:46:44 +00:00
* Copyright ( c ) 2021 , the SerenityOS developers .
2021-03-05 13:03:23 +00:00
*
2021-04-22 08:24:48 +00:00
* SPDX - License - Identifier : BSD - 2 - Clause
2021-03-05 13:03:23 +00:00
*/
# include "Formatter.h"
# include "Shell.h"
# include <LibRegex/Regex.h>
2023-03-21 00:23:24 +00:00
# include <math.h>
2021-03-05 13:03:23 +00:00
namespace Shell {
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_length_impl ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments , bool across )
2021-03-05 13:03:23 +00:00
{
auto name = across ? " length_across " : " length " ;
if ( arguments . size ( ) < 1 | | arguments . size ( ) > 2 ) {
2022-12-04 18:02:33 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , DeprecatedString : : formatted ( " Expected one or two arguments to `{}' " , name ) , invoking_node . position ( ) ) ;
2021-03-05 13:03:23 +00:00
return nullptr ;
}
enum {
Infer ,
String ,
List ,
} mode { Infer } ;
bool is_inferred = false ;
const AST : : Node * expr_node ;
if ( arguments . size ( ) = = 2 ) {
// length string <expr>
// length list <expr>
auto & mode_arg = arguments . first ( ) ;
2023-03-06 13:17:01 +00:00
if ( ! mode_arg - > is_bareword ( ) ) {
raise_error ( ShellError : : EvaluatedSyntaxError , DeprecatedString : : formatted ( " Expected a bareword (either 'string' or 'list') in the two-argument form of the `{}' immediate " , name ) , mode_arg - > position ( ) ) ;
2021-03-05 13:03:23 +00:00
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto const & mode_name = static_cast < const AST : : BarewordLiteral & > ( * mode_arg ) . text ( ) ;
2021-03-05 13:03:23 +00:00
if ( mode_name = = " list " ) {
mode = List ;
} else if ( mode_name = = " string " ) {
mode = String ;
} else if ( mode_name = = " infer " ) {
mode = Infer ;
} else {
2023-03-06 13:17:01 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , DeprecatedString : : formatted ( " Expected either 'string' or 'list' (and not {}) in the two-argument form of the `{}' immediate " , mode_name , name ) , mode_arg - > position ( ) ) ;
2021-03-05 13:03:23 +00:00
return nullptr ;
}
2023-03-06 13:17:01 +00:00
expr_node = arguments [ 1 ] ;
2021-03-05 13:03:23 +00:00
} else {
2023-03-06 13:17:01 +00:00
expr_node = arguments [ 0 ] ;
2021-03-05 13:03:23 +00:00
}
if ( mode = = Infer ) {
is_inferred = true ;
if ( expr_node - > is_list ( ) )
mode = List ;
else if ( expr_node - > is_simple_variable ( ) ) // "Look inside" variables
2023-02-18 13:57:14 +00:00
mode = TRY ( TRY ( const_cast < AST : : Node * > ( expr_node ) - > run ( this ) ) - > resolve_without_cast ( this ) ) - > is_list_without_resolution ( ) ? List : String ;
2021-03-05 13:03:23 +00:00
else if ( is < AST : : ImmediateExpression > ( expr_node ) )
mode = List ;
else
mode = String ;
}
2023-02-18 13:44:19 +00:00
auto value_with_number = [ & ] ( auto number ) - > ErrorOr < NonnullRefPtr < AST : : Node > > {
return AST : : make_ref_counted < AST : : BarewordLiteral > ( invoking_node . position ( ) , TRY ( String : : number ( number ) ) ) ;
2021-03-05 13:03:23 +00:00
} ;
2023-02-18 13:44:19 +00:00
auto do_across = [ & ] ( StringView mode_name , auto & values ) - > ErrorOr < RefPtr < AST : : Node > > {
2021-03-05 13:03:23 +00:00
if ( is_inferred )
2022-07-11 17:32:29 +00:00
mode_name = " infer " sv ;
2021-03-05 13:03:23 +00:00
// Translate to a list of applications of `length <mode_name>`
Vector < NonnullRefPtr < AST : : Node > > resulting_nodes ;
resulting_nodes . ensure_capacity ( values . size ( ) ) ;
for ( auto & entry : values ) {
// ImmediateExpression(length <mode_name> <entry>)
2021-09-02 22:07:29 +00:00
resulting_nodes . unchecked_append ( AST : : make_ref_counted < AST : : ImmediateExpression > (
2021-03-05 13:03:23 +00:00
expr_node - > position ( ) ,
2023-02-28 13:50:29 +00:00
AST : : NameWithPosition { TRY ( " length " _string ) , invoking_node . function_position ( ) } ,
2023-03-06 13:17:01 +00:00
Vector < NonnullRefPtr < AST : : Node > > { Vector < NonnullRefPtr < AST : : Node > > {
2023-02-18 13:44:19 +00:00
static_cast < NonnullRefPtr < AST : : Node > > ( AST : : make_ref_counted < AST : : BarewordLiteral > ( expr_node - > position ( ) , TRY ( String : : from_utf8 ( mode_name ) ) ) ) ,
2021-09-02 22:07:29 +00:00
AST : : make_ref_counted < AST : : SyntheticNode > ( expr_node - > position ( ) , NonnullRefPtr < AST : : Value > ( entry ) ) ,
2021-03-05 13:03:23 +00:00
} } ,
expr_node - > position ( ) ) ) ;
}
2021-09-02 22:07:29 +00:00
return AST : : make_ref_counted < AST : : ListConcatenate > ( invoking_node . position ( ) , move ( resulting_nodes ) ) ;
2021-03-05 13:03:23 +00:00
} ;
switch ( mode ) {
default :
case Infer :
VERIFY_NOT_REACHED ( ) ;
case List : {
2023-02-18 13:57:14 +00:00
auto value = TRY ( const_cast < AST : : Node * > ( expr_node ) - > run ( this ) ) ;
2021-03-05 13:03:23 +00:00
if ( ! value )
return value_with_number ( 0 ) ;
2023-02-18 13:44:19 +00:00
value = TRY ( value - > resolve_without_cast ( this ) ) ;
2021-03-05 13:03:23 +00:00
if ( auto list = dynamic_cast < AST : : ListValue * > ( value . ptr ( ) ) ) {
if ( across )
2022-07-11 17:32:29 +00:00
return do_across ( " list " sv , list - > values ( ) ) ;
2021-03-05 13:03:23 +00:00
return value_with_number ( list - > values ( ) . size ( ) ) ;
}
2023-02-18 13:44:19 +00:00
auto list = TRY ( value - > resolve_as_list ( this ) ) ;
2021-03-05 13:03:23 +00:00
if ( ! across )
return value_with_number ( list . size ( ) ) ;
dbgln ( " List has {} entries " , list . size ( ) ) ;
2021-09-02 22:07:29 +00:00
auto values = AST : : make_ref_counted < AST : : ListValue > ( move ( list ) ) ;
2022-07-11 17:32:29 +00:00
return do_across ( " list " sv , values - > values ( ) ) ;
2021-03-05 13:03:23 +00:00
}
case String : {
// 'across' will only accept lists, and '!across' will only accept non-lists here.
if ( expr_node - > is_list ( ) ) {
if ( ! across ) {
raise_no_list_allowed : ;
Formatter formatter { * expr_node } ;
if ( is_inferred ) {
raise_error ( ShellError : : EvaluatedSyntaxError ,
2022-12-04 18:02:33 +00:00
DeprecatedString : : formatted ( " Could not infer expression type, please explicitly use `{0} string' or `{0} list' " , name ) ,
2021-03-05 13:03:23 +00:00
invoking_node . position ( ) ) ;
return nullptr ;
}
auto source = formatter . format ( ) ;
raise_error ( ShellError : : EvaluatedSyntaxError ,
source . is_empty ( )
? " Invalid application of `length' to a list "
2022-12-04 18:02:33 +00:00
: DeprecatedString : : formatted ( " Invalid application of `length' to a list \n perhaps you meant `{1}length \" {0} \" {2}' or `{1}length_across {0}{2}'? " , source , " \x1b [32m " , " \x1b [0m " ) ,
2021-03-05 13:03:23 +00:00
expr_node - > position ( ) ) ;
return nullptr ;
}
}
2023-02-18 13:57:14 +00:00
auto value = TRY ( const_cast < AST : : Node * > ( expr_node ) - > run ( this ) ) ;
2021-03-05 13:03:23 +00:00
if ( ! value )
return value_with_number ( 0 ) ;
2023-02-18 13:44:19 +00:00
value = TRY ( value - > resolve_without_cast ( * this ) ) ;
2021-03-05 13:03:23 +00:00
if ( auto list = dynamic_cast < AST : : ListValue * > ( value . ptr ( ) ) ) {
if ( ! across )
goto raise_no_list_allowed ;
2022-07-11 17:32:29 +00:00
return do_across ( " string " sv , list - > values ( ) ) ;
2021-03-05 13:03:23 +00:00
}
if ( across & & ! value - > is_list ( ) ) {
Formatter formatter { * expr_node } ;
auto source = formatter . format ( ) ;
raise_error ( ShellError : : EvaluatedSyntaxError ,
2022-12-04 18:02:33 +00:00
DeprecatedString : : formatted ( " Invalid application of `length_across' to a non-list \n perhaps you meant `{1}length {0}{2}'? " , source , " \x1b [32m " , " \x1b [0m " ) ,
2021-03-05 13:03:23 +00:00
expr_node - > position ( ) ) ;
return nullptr ;
}
// Evaluate the nodes and substitute with the lengths.
2023-02-18 13:44:19 +00:00
auto list = TRY ( value - > resolve_as_list ( this ) ) ;
2021-03-05 13:03:23 +00:00
if ( ! expr_node - > is_list ( ) ) {
if ( list . size ( ) = = 1 ) {
if ( across )
goto raise_no_list_allowed ;
// This is the normal case, the expression is a normal non-list expression.
2023-02-18 06:45:08 +00:00
return value_with_number ( list . first ( ) . bytes_as_string_view ( ) . length ( ) ) ;
2021-03-05 13:03:23 +00:00
}
// This can be hit by asking for the length of a command list (e.g. `(>/dev/null)`)
// raise an error about misuse of command lists for now.
// FIXME: What's the length of `(>/dev/null)` supposed to be?
raise_error ( ShellError : : EvaluatedSyntaxError , " Length of meta value (or command list) requested, this is currently not supported. " , expr_node - > position ( ) ) ;
return nullptr ;
}
2021-09-02 22:07:29 +00:00
auto values = AST : : make_ref_counted < AST : : ListValue > ( move ( list ) ) ;
2022-07-11 17:32:29 +00:00
return do_across ( " string " sv , values - > values ( ) ) ;
2021-03-05 13:03:23 +00:00
}
}
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_length ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2021-03-05 13:03:23 +00:00
{
return immediate_length_impl ( invoking_node , arguments , false ) ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_length_across ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2021-03-05 13:03:23 +00:00
{
return immediate_length_impl ( invoking_node , arguments , true ) ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_regex_replace ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2021-03-05 13:03:23 +00:00
{
if ( arguments . size ( ) ! = 3 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 3 arguments to regex_replace " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto pattern = TRY ( const_cast < AST : : Node & > ( * arguments [ 0 ] ) . run ( this ) ) ;
auto replacement = TRY ( const_cast < AST : : Node & > ( * arguments [ 1 ] ) . run ( this ) ) ;
auto value = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments [ 2 ] ) . run ( this ) ) - > resolve_without_cast ( this ) ) ;
2021-03-05 13:03:23 +00:00
if ( ! pattern - > is_string ( ) ) {
2023-03-06 13:17:01 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected the regex_replace pattern to be a string " , arguments [ 0 ] - > position ( ) ) ;
2021-03-05 13:03:23 +00:00
return nullptr ;
}
if ( ! replacement - > is_string ( ) ) {
2023-03-06 13:17:01 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected the regex_replace replacement string to be a string " , arguments [ 1 ] - > position ( ) ) ;
2021-03-05 13:03:23 +00:00
return nullptr ;
}
if ( ! value - > is_string ( ) ) {
2023-03-06 13:17:01 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected the regex_replace target value to be a string " , arguments [ 2 ] - > position ( ) ) ;
2021-03-05 13:03:23 +00:00
return nullptr ;
}
2023-02-18 13:44:19 +00:00
Regex < PosixExtendedParser > re { TRY ( pattern - > resolve_as_list ( this ) ) . first ( ) . to_deprecated_string ( ) } ;
2023-02-18 06:45:08 +00:00
auto result = re . replace (
2023-02-18 13:44:19 +00:00
TRY ( value - > resolve_as_list ( this ) ) [ 0 ] ,
TRY ( replacement - > resolve_as_list ( this ) ) [ 0 ] ,
2023-02-18 06:45:08 +00:00
PosixFlags : : Global | PosixFlags : : Multiline | PosixFlags : : Unicode ) ;
2021-03-05 13:03:23 +00:00
2023-06-11 17:47:20 +00:00
return AST : : make_ref_counted < AST : : StringLiteral > ( invoking_node . position ( ) , TRY ( String : : from_deprecated_string ( result ) ) , AST : : StringLiteral : : EnclosureType : : None ) ;
2021-03-05 13:03:23 +00:00
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_remove_suffix ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2021-03-05 13:03:23 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to remove_suffix " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto suffix = TRY ( const_cast < AST : : Node & > ( * arguments [ 0 ] ) . run ( this ) ) ;
auto value = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments [ 1 ] ) . run ( this ) ) - > resolve_without_cast ( this ) ) ;
2021-03-05 13:03:23 +00:00
if ( ! suffix - > is_string ( ) ) {
2023-03-06 13:17:01 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected the remove_suffix suffix string to be a string " , arguments [ 0 ] - > position ( ) ) ;
2021-03-05 13:03:23 +00:00
return nullptr ;
}
2023-02-18 13:44:19 +00:00
auto suffix_str = TRY ( suffix - > resolve_as_list ( this ) ) [ 0 ] ;
auto values = TRY ( value - > resolve_as_list ( this ) ) ;
2021-03-05 13:03:23 +00:00
Vector < NonnullRefPtr < AST : : Node > > nodes ;
for ( auto & value_str : values ) {
2023-02-18 13:44:19 +00:00
String removed = TRY ( String : : from_utf8 ( value_str ) ) ;
2021-03-05 13:03:23 +00:00
2023-02-18 06:45:08 +00:00
if ( value_str . bytes_as_string_view ( ) . ends_with ( suffix_str ) )
2023-02-18 13:44:19 +00:00
removed = TRY ( removed . substring_from_byte_offset ( 0 , value_str . bytes_as_string_view ( ) . length ( ) - suffix_str . bytes_as_string_view ( ) . length ( ) ) ) ;
2023-02-18 06:45:08 +00:00
nodes . append ( AST : : make_ref_counted < AST : : StringLiteral > ( invoking_node . position ( ) , move ( removed ) , AST : : StringLiteral : : EnclosureType : : None ) ) ;
2021-03-05 13:03:23 +00:00
}
2021-09-02 22:07:29 +00:00
return AST : : make_ref_counted < AST : : ListConcatenate > ( invoking_node . position ( ) , move ( nodes ) ) ;
2021-03-05 13:03:23 +00:00
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_remove_prefix ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2021-03-05 13:03:23 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to remove_prefix " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto prefix = TRY ( const_cast < AST : : Node & > ( * arguments [ 0 ] ) . run ( this ) ) ;
auto value = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments [ 1 ] ) . run ( this ) ) - > resolve_without_cast ( this ) ) ;
2021-03-05 13:03:23 +00:00
if ( ! prefix - > is_string ( ) ) {
2023-03-06 13:17:01 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected the remove_prefix prefix string to be a string " , arguments [ 0 ] - > position ( ) ) ;
2021-03-05 13:03:23 +00:00
return nullptr ;
}
2023-02-18 13:44:19 +00:00
auto prefix_str = TRY ( prefix - > resolve_as_list ( this ) ) [ 0 ] ;
auto values = TRY ( value - > resolve_as_list ( this ) ) ;
2021-03-05 13:03:23 +00:00
Vector < NonnullRefPtr < AST : : Node > > nodes ;
for ( auto & value_str : values ) {
2023-02-18 13:44:19 +00:00
String removed = TRY ( String : : from_utf8 ( value_str ) ) ;
2021-03-05 13:03:23 +00:00
2023-02-18 06:45:08 +00:00
if ( value_str . bytes_as_string_view ( ) . starts_with ( prefix_str ) )
2023-02-18 13:44:19 +00:00
removed = TRY ( removed . substring_from_byte_offset ( prefix_str . bytes_as_string_view ( ) . length ( ) ) ) ;
2023-02-18 06:45:08 +00:00
nodes . append ( AST : : make_ref_counted < AST : : StringLiteral > ( invoking_node . position ( ) , move ( removed ) , AST : : StringLiteral : : EnclosureType : : None ) ) ;
2021-03-05 13:03:23 +00:00
}
2021-09-02 22:07:29 +00:00
return AST : : make_ref_counted < AST : : ListConcatenate > ( invoking_node . position ( ) , move ( nodes ) ) ;
2021-03-05 13:03:23 +00:00
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_split ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2021-03-05 13:03:23 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to split " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto delimiter = TRY ( const_cast < AST : : Node & > ( * arguments [ 0 ] ) . run ( this ) ) ;
auto value = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments [ 1 ] ) . run ( this ) ) - > resolve_without_cast ( this ) ) ;
2021-03-05 13:03:23 +00:00
if ( ! delimiter - > is_string ( ) ) {
2023-03-06 13:17:01 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected the split delimiter string to be a string " , arguments [ 0 ] - > position ( ) ) ;
2021-03-05 13:03:23 +00:00
return nullptr ;
}
2023-02-18 13:44:19 +00:00
auto delimiter_str = TRY ( delimiter - > resolve_as_list ( this ) ) [ 0 ] ;
2021-03-05 13:03:23 +00:00
2022-04-01 17:58:27 +00:00
auto transform = [ & ] ( auto const & values ) {
2021-03-05 13:03:23 +00:00
// Translate to a list of applications of `split <delimiter>`
Vector < NonnullRefPtr < AST : : Node > > resulting_nodes ;
resulting_nodes . ensure_capacity ( values . size ( ) ) ;
for ( auto & entry : values ) {
// ImmediateExpression(split <delimiter> <entry>)
2021-09-02 22:07:29 +00:00
resulting_nodes . unchecked_append ( AST : : make_ref_counted < AST : : ImmediateExpression > (
2023-03-06 13:17:01 +00:00
arguments [ 1 ] - > position ( ) ,
2021-03-05 13:03:23 +00:00
invoking_node . function ( ) ,
2023-03-06 13:17:01 +00:00
Vector < NonnullRefPtr < AST : : Node > > { Vector < NonnullRefPtr < AST : : Node > > {
2021-03-05 13:03:23 +00:00
arguments [ 0 ] ,
2023-03-06 13:17:01 +00:00
AST : : make_ref_counted < AST : : SyntheticNode > ( arguments [ 1 ] - > position ( ) , NonnullRefPtr < AST : : Value > ( entry ) ) ,
2021-03-05 13:03:23 +00:00
} } ,
2023-03-06 13:17:01 +00:00
arguments [ 1 ] - > position ( ) ) ) ;
2021-03-05 13:03:23 +00:00
}
2021-09-02 22:07:29 +00:00
return AST : : make_ref_counted < AST : : ListConcatenate > ( invoking_node . position ( ) , move ( resulting_nodes ) ) ;
2021-03-05 13:03:23 +00:00
} ;
if ( auto list = dynamic_cast < AST : : ListValue * > ( value . ptr ( ) ) ) {
return transform ( list - > values ( ) ) ;
}
// Otherwise, just resolve to a list and transform that.
2023-02-18 13:44:19 +00:00
auto list = TRY ( value - > resolve_as_list ( this ) ) ;
2021-03-05 13:03:23 +00:00
if ( ! value - > is_list ( ) ) {
if ( list . is_empty ( ) )
2023-03-06 13:17:01 +00:00
return AST : : make_ref_counted < AST : : ListConcatenate > ( invoking_node . position ( ) , Vector < NonnullRefPtr < AST : : Node > > { } ) ;
2021-03-05 13:03:23 +00:00
auto & value = list . first ( ) ;
2023-02-18 06:45:08 +00:00
Vector < String > split_strings ;
2021-03-05 13:03:23 +00:00
if ( delimiter_str . is_empty ( ) ) {
StringBuilder builder ;
for ( auto code_point : Utf8View { value } ) {
builder . append_code_point ( code_point ) ;
2023-02-18 13:44:19 +00:00
split_strings . append ( TRY ( builder . to_string ( ) ) ) ;
2021-03-05 13:03:23 +00:00
builder . clear ( ) ;
}
} else {
2022-10-22 13:38:21 +00:00
auto split = StringView { value } . split_view ( delimiter_str , options . inline_exec_keep_empty_segments ? SplitBehavior : : KeepEmpty : SplitBehavior : : Nothing ) ;
2021-03-05 13:03:23 +00:00
split_strings . ensure_capacity ( split . size ( ) ) ;
for ( auto & entry : split )
2023-02-18 13:44:19 +00:00
split_strings . append ( TRY ( String : : from_utf8 ( entry ) ) ) ;
2021-03-05 13:03:23 +00:00
}
2021-09-02 22:07:29 +00:00
return AST : : make_ref_counted < AST : : SyntheticNode > ( invoking_node . position ( ) , AST : : make_ref_counted < AST : : ListValue > ( move ( split_strings ) ) ) ;
2021-03-05 13:03:23 +00:00
}
2021-09-02 22:07:29 +00:00
return transform ( AST : : make_ref_counted < AST : : ListValue > ( list ) - > values ( ) ) ;
2021-03-05 13:03:23 +00:00
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_concat_lists ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2021-03-05 13:03:23 +00:00
{
2023-03-06 13:17:01 +00:00
Vector < NonnullRefPtr < AST : : Node > > result ;
2021-03-05 13:03:23 +00:00
for ( auto & argument : arguments ) {
2023-03-06 13:17:01 +00:00
if ( auto * list = dynamic_cast < AST : : ListConcatenate const * > ( argument . ptr ( ) ) ) {
2021-06-12 11:24:45 +00:00
result . extend ( list - > list ( ) ) ;
2021-03-05 13:03:23 +00:00
} else {
2023-03-06 13:17:01 +00:00
auto list_of_values = TRY ( TRY ( const_cast < AST : : Node & > ( * argument ) . run ( this ) ) - > resolve_without_cast ( this ) ) ;
2021-03-05 13:03:23 +00:00
if ( auto * list = dynamic_cast < AST : : ListValue * > ( list_of_values . ptr ( ) ) ) {
for ( auto & entry : static_cast < Vector < NonnullRefPtr < AST : : Value > > & > ( list - > values ( ) ) )
2023-03-06 13:17:01 +00:00
result . append ( AST : : make_ref_counted < AST : : SyntheticNode > ( argument - > position ( ) , entry ) ) ;
2021-03-05 13:03:23 +00:00
} else {
2023-02-18 13:44:19 +00:00
auto values = TRY ( list_of_values - > resolve_as_list ( this ) ) ;
2021-03-05 13:03:23 +00:00
for ( auto & entry : values )
2023-03-06 13:17:01 +00:00
result . append ( AST : : make_ref_counted < AST : : StringLiteral > ( argument - > position ( ) , entry , AST : : StringLiteral : : EnclosureType : : None ) ) ;
2021-03-05 13:03:23 +00:00
}
}
}
2021-09-02 22:07:29 +00:00
return AST : : make_ref_counted < AST : : ListConcatenate > ( invoking_node . position ( ) , move ( result ) ) ;
2021-03-05 13:03:23 +00:00
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_filter_glob ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2022-02-23 05:46:31 +00:00
{
// filter_glob string list
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly two arguments to filter_glob (<glob> <list>) " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto glob_list = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments [ 0 ] ) . run ( * this ) ) - > resolve_as_list ( * this ) ) ;
2022-02-23 05:46:31 +00:00
if ( glob_list . size ( ) ! = 1 ) {
2023-03-06 13:17:01 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected the <glob> argument to filter_glob to be a single string " , arguments [ 0 ] - > position ( ) ) ;
2022-02-23 05:46:31 +00:00
return nullptr ;
}
auto & glob = glob_list . first ( ) ;
auto & list_node = arguments [ 1 ] ;
2023-03-06 13:17:01 +00:00
Vector < NonnullRefPtr < AST : : Node > > result ;
2022-02-23 05:46:31 +00:00
2023-03-06 13:17:01 +00:00
TRY ( const_cast < AST : : Node & > ( * list_node ) . for_each_entry ( * this , [ & ] ( NonnullRefPtr < AST : : Value > entry ) - > ErrorOr < IterationDecision > {
2023-02-18 13:57:14 +00:00
auto value = TRY ( entry - > resolve_as_list ( * this ) ) ;
2022-02-23 05:46:31 +00:00
if ( value . size ( ) = = 0 )
return IterationDecision : : Continue ;
if ( value . size ( ) = = 1 ) {
2023-02-18 06:45:08 +00:00
if ( ! value . first ( ) . bytes_as_string_view ( ) . matches ( glob ) )
2022-02-23 05:46:31 +00:00
return IterationDecision : : Continue ;
2023-03-06 13:17:01 +00:00
result . append ( AST : : make_ref_counted < AST : : StringLiteral > ( arguments [ 1 ] - > position ( ) , value . first ( ) , AST : : StringLiteral : : EnclosureType : : None ) ) ;
2022-02-23 05:46:31 +00:00
return IterationDecision : : Continue ;
}
for ( auto & entry : value ) {
2023-02-18 06:45:08 +00:00
if ( entry . bytes_as_string_view ( ) . matches ( glob ) ) {
2023-03-06 13:17:01 +00:00
Vector < NonnullRefPtr < AST : : Node > > nodes ;
2022-02-23 05:46:31 +00:00
for ( auto & string : value )
2023-03-06 13:17:01 +00:00
nodes . append ( AST : : make_ref_counted < AST : : StringLiteral > ( arguments [ 1 ] - > position ( ) , string , AST : : StringLiteral : : EnclosureType : : None ) ) ;
result . append ( AST : : make_ref_counted < AST : : ListConcatenate > ( arguments [ 1 ] - > position ( ) , move ( nodes ) ) ) ;
2022-02-23 05:46:31 +00:00
return IterationDecision : : Continue ;
}
}
return IterationDecision : : Continue ;
2023-02-18 13:57:14 +00:00
} ) ) ;
2022-02-23 05:46:31 +00:00
return AST : : make_ref_counted < AST : : ListConcatenate > ( invoking_node . position ( ) , move ( result ) ) ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_join ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2022-02-23 05:46:31 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to join " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto delimiter = TRY ( const_cast < AST : : Node & > ( * arguments [ 0 ] ) . run ( this ) ) ;
2022-02-23 05:46:31 +00:00
if ( ! delimiter - > is_string ( ) ) {
2023-03-06 13:17:01 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected the join delimiter string to be a string " , arguments [ 0 ] - > position ( ) ) ;
2022-02-23 05:46:31 +00:00
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto value = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments [ 1 ] ) . run ( this ) ) - > resolve_without_cast ( this ) ) ;
2022-02-23 05:46:31 +00:00
if ( ! value - > is_list ( ) ) {
2023-03-06 13:17:01 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected the joined list to be a list " , arguments [ 1 ] - > position ( ) ) ;
2022-02-23 05:46:31 +00:00
return nullptr ;
}
2023-02-18 13:44:19 +00:00
auto delimiter_str = TRY ( delimiter - > resolve_as_list ( this ) ) [ 0 ] ;
2022-02-23 05:46:31 +00:00
StringBuilder builder ;
2023-02-18 13:44:19 +00:00
builder . join ( delimiter_str , TRY ( value - > resolve_as_list ( * this ) ) ) ;
2022-02-23 05:46:31 +00:00
2023-02-18 13:44:19 +00:00
return AST : : make_ref_counted < AST : : StringLiteral > ( invoking_node . position ( ) , TRY ( builder . to_string ( ) ) , AST : : StringLiteral : : EnclosureType : : None ) ;
2022-02-23 05:46:31 +00:00
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_value_or_default ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2023-02-11 14:29:15 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to value_or_default " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto name = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . first ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2023-02-18 14:09:41 +00:00
if ( ! TRY ( local_variable_or ( name , " " sv ) ) . is_empty ( ) )
2023-02-11 14:29:15 +00:00
return make_ref_counted < AST : : SimpleVariable > ( invoking_node . position ( ) , name ) ;
return arguments . last ( ) ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_assign_default ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2023-02-11 14:29:15 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to assign_default " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto name = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . first ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2023-02-18 14:09:41 +00:00
if ( ! TRY ( local_variable_or ( name , " " sv ) ) . is_empty ( ) )
2023-02-11 14:29:15 +00:00
return make_ref_counted < AST : : SimpleVariable > ( invoking_node . position ( ) , name ) ;
2023-03-06 13:17:01 +00:00
auto value = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . last ( ) ) . run ( * this ) ) - > resolve_without_cast ( * this ) ) ;
2023-02-18 06:45:08 +00:00
set_local_variable ( name . to_deprecated_string ( ) , value ) ;
2023-02-11 14:29:15 +00:00
return make_ref_counted < AST : : SyntheticNode > ( invoking_node . position ( ) , value ) ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_error_if_empty ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2023-02-11 14:29:15 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to error_if_empty " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto name = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . first ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2023-02-18 14:09:41 +00:00
if ( ! TRY ( local_variable_or ( name , " " sv ) ) . is_empty ( ) )
2023-02-11 14:29:15 +00:00
return make_ref_counted < AST : : SimpleVariable > ( invoking_node . position ( ) , name ) ;
2023-03-06 13:17:01 +00:00
auto error_value = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . last ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2023-02-11 14:29:15 +00:00
if ( error_value . is_empty ( ) )
2023-02-18 13:44:19 +00:00
error_value = TRY ( String : : formatted ( " Expected {} to be non-empty " , name ) ) ;
2023-02-11 14:29:15 +00:00
2023-02-18 06:45:08 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , error_value . bytes_as_string_view ( ) , invoking_node . position ( ) ) ;
2023-02-11 14:29:15 +00:00
return nullptr ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_null_or_alternative ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2023-02-11 14:29:15 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to null_or_alternative " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-04-20 16:44:30 +00:00
auto name = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . first ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
auto frame = find_frame_containing_local_variable ( name ) ;
if ( ! frame )
return make_ref_counted < AST : : StringLiteral > ( invoking_node . position ( ) , " " _short_string , AST : : StringLiteral : : EnclosureType : : None ) ;
auto value = frame - > local_variables . get ( name . bytes_as_string_view ( ) ) . value ( ) ;
2023-02-18 13:44:19 +00:00
if ( ( value - > is_string ( ) & & TRY ( value - > resolve_as_string ( * this ) ) . is_empty ( ) ) | | ( value - > is_list ( ) & & TRY ( value - > resolve_as_list ( * this ) ) . is_empty ( ) ) )
2023-04-20 16:44:30 +00:00
return make_ref_counted < AST : : SyntheticNode > ( invoking_node . position ( ) , * value ) ;
2023-02-11 14:29:15 +00:00
return arguments . last ( ) ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_defined_value_or_default ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2023-02-11 14:29:15 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to defined_value_or_default " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto name = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . first ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2023-02-11 14:29:15 +00:00
if ( ! find_frame_containing_local_variable ( name ) )
return arguments . last ( ) ;
return make_ref_counted < AST : : SimpleVariable > ( invoking_node . position ( ) , name ) ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_assign_defined_default ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2023-02-11 14:29:15 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to assign_defined_default " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto name = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . first ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2023-02-11 14:29:15 +00:00
if ( find_frame_containing_local_variable ( name ) )
return make_ref_counted < AST : : SimpleVariable > ( invoking_node . position ( ) , name ) ;
2023-03-06 13:17:01 +00:00
auto value = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . last ( ) ) . run ( * this ) ) - > resolve_without_cast ( * this ) ) ;
2023-02-18 06:45:08 +00:00
set_local_variable ( name . to_deprecated_string ( ) , value ) ;
2023-02-11 14:29:15 +00:00
return make_ref_counted < AST : : SyntheticNode > ( invoking_node . position ( ) , value ) ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_error_if_unset ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2023-02-11 14:29:15 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to error_if_unset " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto name = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . first ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2023-02-11 14:29:15 +00:00
if ( find_frame_containing_local_variable ( name ) )
return make_ref_counted < AST : : SimpleVariable > ( invoking_node . position ( ) , name ) ;
2023-03-06 13:17:01 +00:00
auto error_value = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . last ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2023-02-11 14:29:15 +00:00
if ( error_value . is_empty ( ) )
2023-02-18 13:44:19 +00:00
error_value = TRY ( String : : formatted ( " Expected {} to be set " , name ) ) ;
2023-02-11 14:29:15 +00:00
2023-02-18 06:45:08 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , error_value . bytes_as_string_view ( ) , invoking_node . position ( ) ) ;
2023-02-11 14:29:15 +00:00
return nullptr ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_null_if_unset_or_alternative ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2023-02-11 14:29:15 +00:00
{
if ( arguments . size ( ) ! = 2 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 2 arguments to null_if_unset_or_alternative " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto name = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . first ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2023-02-11 14:29:15 +00:00
if ( ! find_frame_containing_local_variable ( name ) )
return arguments . last ( ) ;
return make_ref_counted < AST : : SimpleVariable > ( invoking_node . position ( ) , name ) ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_reexpand ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2023-02-11 14:29:15 +00:00
{
if ( arguments . size ( ) ! = 1 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 1 argument to reexpand " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto value = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . first ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2023-02-11 14:29:15 +00:00
return parse ( value , m_is_interactive , false ) ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_length_of_variable ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2023-02-11 14:29:15 +00:00
{
if ( arguments . size ( ) ! = 1 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 1 argument to length_of_variable " , invoking_node . position ( ) ) ;
return nullptr ;
}
2023-03-06 13:17:01 +00:00
auto name = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . first ( ) ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2023-02-11 14:29:15 +00:00
auto variable = make_ref_counted < AST : : SimpleVariable > ( invoking_node . position ( ) , name ) ;
return immediate_length_impl (
invoking_node ,
{ move ( variable ) } ,
false ) ;
}
2023-03-21 00:23:24 +00:00
namespace Arithmetic {
struct BinaryOperationNode ;
struct UnaryOperationNode ;
struct TernaryOperationNode ;
struct ErrorNode ;
struct Node {
Variant < String , i64 , NonnullOwnPtr < BinaryOperationNode > , NonnullOwnPtr < UnaryOperationNode > , NonnullOwnPtr < TernaryOperationNode > , NonnullOwnPtr < ErrorNode > > value ;
} ;
struct ErrorNode {
String error ;
} ;
enum class Operator {
Add , // +
Subtract , // -
Multiply , // *
Quotient , // /
Remainder , // %
Power , // **
Equal , // ==
GreaterThan , // >
LessThan , // <
NotEqual , // !=
GreaterThanOrEqual , // >=
LessThanOrEqual , // <=
BitwiseAnd , // &
BitwiseOr , // |
BitwiseXor , // ^
ShiftLeft , // <<
ShiftRight , // >>
ArithmeticAnd , // &&
ArithmeticOr , // ||
Comma , // ,
Negate , // !
BitwiseNegate , // ~
TernaryQuestion , // ?
TernaryColon , // :
Assignment , // =
PlusAssignment , // +=
MinusAssignment , // -=
MultiplyAssignment , // *=
DivideAssignment , // /=
ModuloAssignment , // %=
AndAssignment , // &=
OrAssignment , // |=
XorAssignment , // ^=
LeftShiftAssignment , // <<=
RightShiftAssignment , // >>=
OpenParen , // (
CloseParen , // )
} ;
static Operator assignment_operation_of ( Operator op )
{
switch ( op ) {
case Operator : : PlusAssignment :
return Operator : : Add ;
case Operator : : MinusAssignment :
return Operator : : Subtract ;
case Operator : : MultiplyAssignment :
return Operator : : Multiply ;
case Operator : : DivideAssignment :
return Operator : : Quotient ;
case Operator : : ModuloAssignment :
return Operator : : Remainder ;
case Operator : : AndAssignment :
return Operator : : BitwiseAnd ;
case Operator : : OrAssignment :
return Operator : : BitwiseOr ;
case Operator : : XorAssignment :
return Operator : : BitwiseXor ;
case Operator : : LeftShiftAssignment :
return Operator : : ShiftLeft ;
case Operator : : RightShiftAssignment :
return Operator : : ShiftRight ;
default :
VERIFY_NOT_REACHED ( ) ;
}
}
static bool is_assignment_operator ( Operator op )
{
switch ( op ) {
case Operator : : Assignment :
case Operator : : PlusAssignment :
case Operator : : MinusAssignment :
case Operator : : MultiplyAssignment :
case Operator : : DivideAssignment :
case Operator : : ModuloAssignment :
case Operator : : AndAssignment :
case Operator : : OrAssignment :
case Operator : : XorAssignment :
case Operator : : LeftShiftAssignment :
case Operator : : RightShiftAssignment :
return true ;
default :
return false ;
}
}
using Token = Variant < String , i64 , Operator > ;
struct BinaryOperationNode {
BinaryOperationNode ( Operator op , Node lhs , Node rhs )
: op ( op )
, lhs ( move ( lhs ) )
, rhs ( move ( rhs ) )
{
}
Operator op ;
Node lhs ;
Node rhs ;
} ;
struct UnaryOperationNode {
UnaryOperationNode ( Operator op , Node rhs )
: op ( op )
, rhs ( move ( rhs ) )
{
}
Operator op ;
Node rhs ;
} ;
struct TernaryOperationNode {
TernaryOperationNode ( Node condition , Node true_value , Node false_value )
: condition ( move ( condition ) )
, true_value ( move ( true_value ) )
, false_value ( move ( false_value ) )
{
}
Node condition ;
Node true_value ;
Node false_value ;
} ;
static ErrorOr < Node > parse_expression ( Span < Token > ) ;
static ErrorOr < Node > parse_assignment_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_comma_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_ternary_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_logical_or_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_logical_and_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_bitwise_or_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_bitwise_xor_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_bitwise_and_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_equality_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_comparison_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_shift_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_additive_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_multiplicative_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_exponential_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_unary_expression ( Span < Token > & ) ;
static ErrorOr < Node > parse_primary_expression ( Span < Token > & ) ;
template < size_t N >
static ErrorOr < Node > parse_binary_expression_using_operators ( Span < Token > & , Array < Operator , N > , Function < ErrorOr < Node > ( Span < Token > & ) > const & parse_rhs ) ;
static ErrorOr < Node > parse_binary_expression_using_operator ( Span < Token > & tokens , Operator op , Function < ErrorOr < Node > ( Span < Token > & ) > const & parse_rhs )
{
return parse_binary_expression_using_operators ( tokens , Array { op } , parse_rhs ) ;
}
static bool next_token_is_operator ( Span < Token > & tokens , Operator op )
{
if ( tokens . is_empty ( ) )
return false ;
return tokens . first ( ) . has < Operator > ( ) & & tokens . first ( ) . get < Operator > ( ) = = op ;
}
ErrorOr < Node > parse_expression ( Span < Token > tokens )
{
return parse_comma_expression ( tokens ) ;
}
ErrorOr < Node > parse_comma_expression ( Span < Token > & tokens )
{
return parse_binary_expression_using_operator ( tokens , Operator : : Comma , & parse_assignment_expression ) ;
}
ErrorOr < Node > parse_assignment_expression ( Span < Token > & tokens )
{
auto lhs = TRY ( parse_ternary_expression ( tokens ) ) ;
if ( tokens . is_empty ( ) )
return lhs ;
auto is_assignment_operator = [ ] ( Operator op ) {
return op = = Operator : : Assignment
| | op = = Operator : : PlusAssignment
| | op = = Operator : : MinusAssignment
| | op = = Operator : : MultiplyAssignment
| | op = = Operator : : DivideAssignment
| | op = = Operator : : ModuloAssignment
| | op = = Operator : : AndAssignment
| | op = = Operator : : OrAssignment
| | op = = Operator : : XorAssignment
| | op = = Operator : : LeftShiftAssignment
| | op = = Operator : : RightShiftAssignment ;
} ;
auto & token = tokens . first ( ) ;
if ( auto op = token . get_pointer < Operator > ( ) ; op & & is_assignment_operator ( * op ) ) {
if ( ! lhs . value . has < String > ( ) ) {
return Node {
make < ErrorNode > ( TRY ( " Left-hand side of assignment must be a variable " _string ) )
} ;
}
tokens = tokens . slice ( 1 ) ;
auto rhs = TRY ( parse_assignment_expression ( tokens ) ) ;
return Node {
make < BinaryOperationNode > ( * op , move ( lhs ) , move ( rhs ) )
} ;
}
return lhs ;
}
ErrorOr < Node > parse_ternary_expression ( Span < Token > & tokens )
{
auto condition = TRY ( parse_logical_or_expression ( tokens ) ) ;
if ( ! next_token_is_operator ( tokens , Operator : : TernaryQuestion ) )
return condition ;
tokens = tokens . slice ( 1 ) ;
auto true_value = TRY ( parse_comma_expression ( tokens ) ) ;
if ( ! next_token_is_operator ( tokens , Operator : : TernaryColon ) ) {
return Node {
make < ErrorNode > ( TRY ( " Expected ':' after true value in ternary expression " _string ) )
} ;
}
tokens = tokens . slice ( 1 ) ;
auto false_value = TRY ( parse_ternary_expression ( tokens ) ) ;
return Node {
make < TernaryOperationNode > ( move ( condition ) , move ( true_value ) , move ( false_value ) )
} ;
}
ErrorOr < Node > parse_logical_or_expression ( Span < Token > & tokens )
{
return parse_binary_expression_using_operator ( tokens , Operator : : ArithmeticOr , & parse_logical_and_expression ) ;
}
ErrorOr < Node > parse_logical_and_expression ( Span < Token > & tokens )
{
return parse_binary_expression_using_operator ( tokens , Operator : : ArithmeticAnd , & parse_bitwise_or_expression ) ;
}
ErrorOr < Node > parse_bitwise_or_expression ( Span < Token > & tokens )
{
return parse_binary_expression_using_operator ( tokens , Operator : : BitwiseOr , & parse_bitwise_xor_expression ) ;
}
ErrorOr < Node > parse_bitwise_xor_expression ( Span < Token > & tokens )
{
return parse_binary_expression_using_operator ( tokens , Operator : : BitwiseXor , & parse_bitwise_and_expression ) ;
}
ErrorOr < Node > parse_bitwise_and_expression ( Span < Token > & tokens )
{
return parse_binary_expression_using_operator ( tokens , Operator : : BitwiseAnd , & parse_equality_expression ) ;
}
ErrorOr < Node > parse_equality_expression ( Span < Token > & tokens )
{
return parse_binary_expression_using_operators ( tokens , Array { Operator : : Equal , Operator : : NotEqual } , & parse_comparison_expression ) ;
}
ErrorOr < Node > parse_comparison_expression ( Span < Token > & tokens )
{
return parse_binary_expression_using_operators ( tokens , Array { Operator : : LessThan , Operator : : GreaterThan , Operator : : LessThanOrEqual , Operator : : GreaterThanOrEqual } , & parse_shift_expression ) ;
}
ErrorOr < Node > parse_shift_expression ( Span < Token > & tokens )
{
return parse_binary_expression_using_operators ( tokens , Array { Operator : : ShiftLeft , Operator : : ShiftRight } , & parse_additive_expression ) ;
}
ErrorOr < Node > parse_additive_expression ( Span < Token > & tokens )
{
return parse_binary_expression_using_operators ( tokens , Array { Operator : : Add , Operator : : Subtract } , & parse_multiplicative_expression ) ;
}
ErrorOr < Node > parse_multiplicative_expression ( Span < Token > & tokens )
{
return parse_binary_expression_using_operators ( tokens , Array { Operator : : Multiply , Operator : : Quotient , Operator : : Remainder } , & parse_exponential_expression ) ;
}
ErrorOr < Node > parse_exponential_expression ( Span < Token > & tokens )
{
auto lhs = TRY ( parse_unary_expression ( tokens ) ) ;
if ( ! next_token_is_operator ( tokens , Operator : : Power ) )
return lhs ;
tokens = tokens . slice ( 1 ) ;
auto rhs = TRY ( parse_exponential_expression ( tokens ) ) ;
return Node {
make < BinaryOperationNode > ( Operator : : Power , move ( lhs ) , move ( rhs ) )
} ;
}
ErrorOr < Node > parse_unary_expression ( Span < Token > & tokens )
{
if ( tokens . is_empty ( ) ) {
return Node {
make < ErrorNode > ( TRY ( " Expected expression, got end of input " _string ) )
} ;
}
auto & token = tokens . first ( ) ;
if ( auto op = token . get_pointer < Operator > ( ) ) {
if ( * op = = Operator : : Add | | * op = = Operator : : Subtract | | * op = = Operator : : Negate | | * op = = Operator : : BitwiseNegate ) {
tokens = tokens . slice ( 1 ) ;
auto rhs = TRY ( parse_unary_expression ( tokens ) ) ;
return Node {
make < UnaryOperationNode > ( * op , move ( rhs ) )
} ;
}
}
return parse_primary_expression ( tokens ) ;
}
ErrorOr < Node > parse_primary_expression ( Span < Token > & tokens )
{
if ( tokens . is_empty ( ) )
return Node { make < ErrorNode > ( TRY ( " Expected expression, got end of input " _string ) ) } ;
auto & token = tokens . first ( ) ;
return token . visit (
[ & ] ( String const & var ) - > ErrorOr < Node > {
tokens = tokens . slice ( 1 ) ;
return Node { var } ;
} ,
[ & ] ( i64 value ) - > ErrorOr < Node > {
tokens = tokens . slice ( 1 ) ;
return Node { value } ;
} ,
[ & ] ( Operator op ) - > ErrorOr < Node > {
switch ( op ) {
case Operator : : OpenParen : {
tokens = tokens . slice ( 1 ) ;
auto value = TRY ( parse_expression ( tokens ) ) ;
if ( ! next_token_is_operator ( tokens , Operator : : CloseParen ) ) {
return Node {
make < ErrorNode > ( TRY ( " Expected ')' after expression in parentheses " _string ) )
} ;
}
tokens = tokens . slice ( 1 ) ;
return value ;
}
default :
return Node {
make < ErrorNode > ( TRY ( " Expected expression, got operator " _string ) )
} ;
}
} ) ;
}
template < size_t N >
ErrorOr < Node > parse_binary_expression_using_operators ( Span < Token > & tokens , Array < Operator , N > operators , Function < ErrorOr < Node > ( Span < Token > & ) > const & parse_rhs )
{
auto lhs = TRY ( parse_rhs ( tokens ) ) ;
for ( ; ; ) {
Optional < Operator > op ;
for ( auto candidate : operators ) {
if ( next_token_is_operator ( tokens , candidate ) ) {
op = candidate ;
break ;
}
}
if ( ! op . has_value ( ) )
return lhs ;
tokens = tokens . slice ( 1 ) ;
auto rhs = TRY ( parse_rhs ( tokens ) ) ;
lhs = Node {
make < BinaryOperationNode > ( * op , move ( lhs ) , move ( rhs ) )
} ;
}
}
}
ErrorOr < RefPtr < AST : : Node > > Shell : : immediate_math ( AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
{
if ( arguments . size ( ) ! = 1 ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Expected exactly 1 argument to math " , invoking_node . position ( ) ) ;
return nullptr ;
}
auto expression_parts = TRY ( TRY ( const_cast < AST : : Node & > ( * arguments . first ( ) ) . run ( * this ) ) - > resolve_as_list ( * this ) ) ;
auto expression = TRY ( String : : join ( ' ' , expression_parts ) ) ;
using Arithmetic : : Operator ;
using Arithmetic : : Token ;
Vector < Token > tokens ;
auto view = expression . code_points ( ) ;
Optional < size_t > integer_or_word_start_offset ;
for ( auto it = view . begin ( ) ; it ! = view . end ( ) ; + + it ) {
auto code_point = * it ;
if ( is_ascii_alphanumeric ( code_point ) | | code_point = = U ' _ ' ) {
if ( ! integer_or_word_start_offset . has_value ( ) )
integer_or_word_start_offset = view . byte_offset_of ( it ) ;
continue ;
}
if ( integer_or_word_start_offset . has_value ( ) ) {
auto integer_or_word = view . substring_view (
* integer_or_word_start_offset ,
view . byte_offset_of ( it ) - * integer_or_word_start_offset ) ;
if ( all_of ( integer_or_word , is_ascii_digit ) )
tokens . append ( * integer_or_word . as_string ( ) . to_int ( ) ) ;
else
tokens . append ( TRY ( expression . substring_from_byte_offset_with_shared_superstring ( * integer_or_word_start_offset , integer_or_word . length ( ) ) ) ) ;
integer_or_word_start_offset . clear ( ) ;
}
switch ( code_point ) {
case U ' ! ' :
if ( it . peek ( 1 ) = = U ' = ' ) {
+ + it ;
tokens . append ( Operator : : NotEqual ) ;
} else {
tokens . append ( Operator : : Negate ) ;
}
break ;
case U ' = ' :
if ( it . peek ( 1 ) = = U ' = ' ) {
+ + it ;
tokens . append ( Operator : : Equal ) ;
} else {
tokens . append ( Operator : : Assignment ) ;
}
break ;
case U ' ~ ' :
tokens . append ( Operator : : BitwiseNegate ) ;
break ;
case U ' ( ' :
tokens . append ( Operator : : OpenParen ) ;
break ;
case U ' ) ' :
tokens . append ( Operator : : CloseParen ) ;
break ;
case U ' & ' :
switch ( it . peek ( 1 ) . value_or ( 0 ) ) {
case U ' & ' :
+ + it ;
tokens . append ( Operator : : ArithmeticAnd ) ;
break ;
case U ' = ' :
+ + it ;
tokens . append ( Operator : : AndAssignment ) ;
break ;
default :
tokens . append ( Operator : : BitwiseAnd ) ;
break ;
}
break ;
case U ' | ' :
switch ( it . peek ( 1 ) . value_or ( 0 ) ) {
case U ' | ' :
+ + it ;
tokens . append ( Operator : : ArithmeticOr ) ;
break ;
case U ' = ' :
+ + it ;
tokens . append ( Operator : : OrAssignment ) ;
break ;
default :
tokens . append ( Operator : : BitwiseOr ) ;
break ;
}
break ;
case U ' ^ ' :
if ( it . peek ( 1 ) = = U ' = ' ) {
+ + it ;
tokens . append ( Operator : : XorAssignment ) ;
} else {
tokens . append ( Operator : : BitwiseXor ) ;
}
break ;
case U ' , ' :
tokens . append ( Operator : : Comma ) ;
break ;
case U ' ? ' :
tokens . append ( Operator : : TernaryQuestion ) ;
break ;
case U ' : ' :
tokens . append ( Operator : : TernaryColon ) ;
break ;
case U ' + ' :
switch ( it . peek ( 1 ) . value_or ( 0 ) ) {
case U ' = ' :
+ + it ;
tokens . append ( Operator : : PlusAssignment ) ;
break ;
default :
tokens . append ( Operator : : Add ) ;
break ;
}
break ;
case U ' - ' :
switch ( it . peek ( 1 ) . value_or ( 0 ) ) {
case U ' = ' :
+ + it ;
tokens . append ( Operator : : MinusAssignment ) ;
break ;
default :
tokens . append ( Operator : : Subtract ) ;
break ;
}
break ;
case U ' * ' :
switch ( it . peek ( 1 ) . value_or ( 0 ) ) {
case U ' = ' :
+ + it ;
tokens . append ( Operator : : MultiplyAssignment ) ;
break ;
case U ' * ' :
+ + it ;
tokens . append ( Operator : : Power ) ;
break ;
default :
tokens . append ( Operator : : Multiply ) ;
break ;
}
break ;
case U ' / ' :
if ( it . peek ( 1 ) = = U ' = ' ) {
+ + it ;
tokens . append ( Operator : : DivideAssignment ) ;
} else {
tokens . append ( Operator : : Quotient ) ;
}
break ;
case U ' % ' :
if ( it . peek ( 1 ) = = U ' = ' ) {
+ + it ;
tokens . append ( Operator : : ModuloAssignment ) ;
} else {
tokens . append ( Operator : : Remainder ) ;
}
break ;
case U ' < ' :
switch ( it . peek ( 1 ) . value_or ( 0 ) ) {
case U ' < ' :
+ + it ;
if ( it . peek ( 1 ) = = U ' = ' ) {
+ + it ;
tokens . append ( Operator : : LeftShiftAssignment ) ;
} else {
tokens . append ( Operator : : ShiftLeft ) ;
}
break ;
case U ' = ' :
+ + it ;
tokens . append ( Operator : : LessThanOrEqual ) ;
break ;
default :
tokens . append ( Operator : : LessThan ) ;
break ;
}
break ;
case U ' > ' :
switch ( it . peek ( 1 ) . value_or ( 0 ) ) {
case U ' > ' :
+ + it ;
if ( it . peek ( 1 ) = = U ' = ' ) {
+ + it ;
tokens . append ( Operator : : RightShiftAssignment ) ;
} else {
tokens . append ( Operator : : ShiftRight ) ;
}
break ;
case U ' = ' :
+ + it ;
tokens . append ( Operator : : GreaterThanOrEqual ) ;
break ;
default :
tokens . append ( Operator : : GreaterThan ) ;
break ;
}
break ;
case U ' ' :
case U ' \t ' :
case U ' \n ' :
case U ' \r ' :
break ;
default :
raise_error ( ShellError : : EvaluatedSyntaxError , DeprecatedString : : formatted ( " Unexpected character '{:c}' in math expression " , code_point ) , arguments . first ( ) - > position ( ) ) ;
return nullptr ;
}
}
if ( integer_or_word_start_offset . has_value ( ) ) {
auto integer_or_word = view . substring_view ( * integer_or_word_start_offset ) ;
if ( all_of ( integer_or_word , is_ascii_digit ) )
tokens . append ( * integer_or_word . as_string ( ) . to_int ( ) ) ;
else
tokens . append ( TRY ( expression . substring_from_byte_offset_with_shared_superstring ( * integer_or_word_start_offset , integer_or_word . length ( ) ) ) ) ;
integer_or_word_start_offset . clear ( ) ;
}
auto ast = TRY ( Arithmetic : : parse_expression ( tokens ) ) ;
// Now interpret that.
Function < ErrorOr < i64 > ( Arithmetic : : Node const & ) > interpret = [ & ] ( Arithmetic : : Node const & node ) - > ErrorOr < i64 > {
return node . value . visit (
[ & ] ( String const & name ) - > ErrorOr < i64 > {
size_t resolution_attempts_remaining = 100 ;
for ( auto resolved_name = name ; resolution_attempts_remaining > 0 ; - - resolution_attempts_remaining ) {
2023-04-29 17:03:37 +00:00
auto value = TRY ( look_up_local_variable ( resolved_name . bytes_as_string_view ( ) ) ) ;
2023-03-21 00:23:24 +00:00
if ( ! value )
break ;
StringBuilder builder ;
builder . join ( ' ' , TRY ( const_cast < AST : : Value & > ( * value ) . resolve_as_list ( const_cast < Shell & > ( * this ) ) ) ) ;
resolved_name = TRY ( builder . to_string ( ) ) ;
auto integer = resolved_name . bytes_as_string_view ( ) . to_int < i64 > ( ) ;
if ( integer . has_value ( ) )
return * integer ;
}
if ( resolution_attempts_remaining = = 0 )
raise_error ( ShellError : : EvaluatedSyntaxError , DeprecatedString : : formatted ( " Too many indirections when resolving variable '{}' " , name ) , arguments . first ( ) - > position ( ) ) ;
return 0 ;
} ,
[ & ] ( i64 value ) - > ErrorOr < i64 > {
return value ;
} ,
[ & ] ( NonnullOwnPtr < Arithmetic : : BinaryOperationNode > const & node ) - > ErrorOr < i64 > {
if ( Arithmetic : : is_assignment_operator ( node - > op ) ) {
// lhs must be a variable name.
auto name = node - > lhs . value . get_pointer < String > ( ) ;
if ( ! name ) {
raise_error ( ShellError : : EvaluatedSyntaxError , " Invalid left-hand side of assignment " , arguments . first ( ) - > position ( ) ) ;
return 0 ;
}
auto rhs = TRY ( interpret ( node - > rhs ) ) ;
if ( node - > op ! = Arithmetic : : Operator : : Assignment ) {
// Evaluate the new value
rhs = TRY ( interpret ( Arithmetic : : Node {
. value = make < Arithmetic : : BinaryOperationNode > (
Arithmetic : : assignment_operation_of ( node - > op ) ,
Arithmetic : : Node { * name } ,
Arithmetic : : Node { rhs } ) ,
} ) ) ;
}
set_local_variable ( name - > to_deprecated_string ( ) , make_ref_counted < AST : : StringValue > ( TRY ( String : : number ( rhs ) ) ) ) ;
return rhs ;
}
auto lhs = TRY ( interpret ( node - > lhs ) ) ;
auto rhs = TRY ( interpret ( node - > rhs ) ) ;
using Arithmetic : : Operator ;
switch ( node - > op ) {
case Operator : : Add :
return lhs + rhs ;
case Operator : : Subtract :
return lhs - rhs ;
case Operator : : Multiply :
return lhs * rhs ;
case Operator : : Quotient :
return lhs / rhs ;
case Operator : : Remainder :
return lhs % rhs ;
case Operator : : ShiftLeft :
return lhs < < rhs ;
case Operator : : ShiftRight :
return lhs > > rhs ;
case Operator : : BitwiseAnd :
return lhs & rhs ;
case Operator : : BitwiseOr :
return lhs | rhs ;
case Operator : : BitwiseXor :
return lhs ^ rhs ;
case Operator : : ArithmeticAnd :
return lhs ! = 0 & & rhs ! = 0 ;
case Operator : : ArithmeticOr :
return lhs ! = 0 | | rhs ! = 0 ;
case Operator : : LessThan :
return lhs < rhs ;
case Operator : : LessThanOrEqual :
return lhs < = rhs ;
case Operator : : GreaterThan :
return lhs > rhs ;
case Operator : : GreaterThanOrEqual :
return lhs > = rhs ;
case Operator : : Equal :
return lhs = = rhs ;
case Operator : : NotEqual :
return lhs ! = rhs ;
case Operator : : Power :
return trunc ( pow ( static_cast < double > ( lhs ) , static_cast < double > ( rhs ) ) ) ;
case Operator : : Comma :
return rhs ;
default :
VERIFY_NOT_REACHED ( ) ;
}
} ,
[ & ] ( NonnullOwnPtr < Arithmetic : : UnaryOperationNode > const & node ) - > ErrorOr < i64 > {
auto value = TRY ( interpret ( node - > rhs ) ) ;
switch ( node - > op ) {
case Arithmetic : : Operator : : Negate :
return value = = 0 ;
case Arithmetic : : Operator : : BitwiseNegate :
return ~ value ;
case Arithmetic : : Operator : : Add :
return value ;
case Arithmetic : : Operator : : Subtract :
return - value ;
default :
VERIFY_NOT_REACHED ( ) ;
}
} ,
[ & ] ( NonnullOwnPtr < Arithmetic : : TernaryOperationNode > const & node ) - > ErrorOr < i64 > {
auto condition = TRY ( interpret ( node - > condition ) ) ;
if ( condition ! = 0 )
return TRY ( interpret ( node - > true_value ) ) ;
return TRY ( interpret ( node - > false_value ) ) ;
} ,
[ & ] ( NonnullOwnPtr < Arithmetic : : ErrorNode > const & node ) - > ErrorOr < i64 > {
raise_error ( ShellError : : EvaluatedSyntaxError , node - > error . to_deprecated_string ( ) , arguments . first ( ) - > position ( ) ) ;
return 0 ;
} ) ;
} ;
auto result = TRY ( interpret ( ast ) ) ;
return make_ref_counted < AST : : StringLiteral > ( arguments . first ( ) - > position ( ) , TRY ( String : : number ( result ) ) , AST : : StringLiteral : : EnclosureType : : None ) ;
}
2023-03-06 13:17:01 +00:00
ErrorOr < RefPtr < AST : : Node > > Shell : : run_immediate_function ( StringView str , AST : : ImmediateExpression & invoking_node , Vector < NonnullRefPtr < AST : : Node > > const & arguments )
2021-03-05 13:03:23 +00:00
{
# define __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(name) \
if ( str = = # name ) \
return immediate_ # # name ( invoking_node , arguments ) ;
ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS ( )
# undef __ENUMERATE_SHELL_IMMEDIATE_FUNCTION
2022-12-04 18:02:33 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , DeprecatedString : : formatted ( " Unknown immediate function {} " , str ) , invoking_node . position ( ) ) ;
2021-03-05 13:03:23 +00:00
return nullptr ;
}
2021-11-10 23:55:02 +00:00
bool Shell : : has_immediate_function ( StringView str )
2021-03-05 13:03:23 +00:00
{
# define __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(name) \
if ( str = = # name ) \
return true ;
ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS ( )
# undef __ENUMERATE_SHELL_IMMEDIATE_FUNCTION
return false ;
}
}