1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 12:28:12 +00:00

Shell: Add support for heredocs

Closes #4283.
Heredocs are implemented in a way that makes them feel more like a
string (and not a weird redirection, a la bash).
There are two tunables, whether the string is dedented (`<<-` vs `<<~`)
and whether it allows interpolation (quoted key vs not).
To the familiar people, this is how Ruby handles them, and I feel is the
most elegant heredoc syntax.
Unlike the oddjob that is bash, heredocs are treated exactly as normal
strings, and can be used _anywhere_ where a string can be used.
They are *required* to appear in the same order as used after a newline
is seen when parsing the sequence that the heredoc is used in.
For instance:
```sh
echo <<-doc1 <<-doc2 | blah blah
contents for doc1
doc1
contents for doc2
doc2
```
The typical nice errors are also implemented :^)
This commit is contained in:
Ali Mohammad Pur 2021-04-29 07:04:00 +04:30 committed by Andreas Kling
parent 7c8d39e002
commit 3048274f5e
7 changed files with 364 additions and 10 deletions

View file

@ -46,6 +46,13 @@ private:
ShouldReadMoreSequences decision;
};
struct HeredocInitiationRecord {
String end;
RefPtr<AST::Heredoc> node;
bool interpolate { false };
bool deindent { false };
};
constexpr static size_t max_allowed_nested_rule_depth = 2048;
RefPtr<AST::Node> parse_toplevel();
SequenceParseResult parse_sequence();
@ -81,11 +88,19 @@ private:
RefPtr<AST::Node> parse_brace_expansion();
RefPtr<AST::Node> parse_brace_expansion_spec();
RefPtr<AST::Node> parse_immediate_expression();
RefPtr<AST::Node> parse_heredoc_initiation_record();
bool parse_heredoc_entries();
template<typename A, typename... Args>
NonnullRefPtr<A> create(Args... args);
bool at_end() const { return m_input.length() <= m_offset; }
void set_end_condition(Function<bool()> condition) { m_end_condition = move(condition); }
bool at_end() const
{
if (m_end_condition && m_end_condition())
return true;
return m_input.length() <= m_offset;
}
char peek();
char consume();
bool expect(char);
@ -144,6 +159,8 @@ private:
Vector<size_t> m_rule_start_offsets;
Vector<AST::Position::Line> m_rule_start_lines;
Function<bool()> m_end_condition;
Vector<HeredocInitiationRecord> m_heredoc_initiations;
Vector<char> m_extra_chars_not_allowed_in_barewords;
bool m_is_in_brace_expansion_spec { false };
bool m_continuation_controls_allowed { false };
@ -169,7 +186,9 @@ and_logical_sequence :: pipe_sequence '&' '&' and_logical_sequence
| pipe_sequence
terminator :: ';'
| '\n'
| '\n' [?!heredoc_stack.is_empty] heredoc_entries
heredoc_entries :: { .*? (heredoc_entry) '\n' } [each heredoc_entries]
variable_decls :: identifier '=' expression (' '+ variable_decls)? ' '*
| identifier '=' '(' pipe_sequence ')' (' '+ variable_decls)? ' '*
@ -233,6 +252,12 @@ string_composite :: string string_composite?
| bareword string_composite?
| glob string_composite?
| brace_expansion string_composite?
| heredoc_initiator string_composite? {append to heredoc_entries}
heredoc_initiator :: '<' '<' '-' bareword {*bareword, interpolate, no deindent}
| '<' '<' '-' "'" [^']* "'" {*string, no interpolate, no deindent}
| '<' '<' '~' bareword {*bareword, interpolate, deindent}
| '<' '<' '~' "'" [^']* "'" {*bareword, no interpolate, deindent}
string :: '"' dquoted_string_inner '"'
| "'" [^']* "'"