/*
Please use git log for copyright holder and year information
This file is part of libbash.
libbash is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
libbash is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with libbash. If not, see .
*/
#ifdef OUTPUT_C
grammar libbash;
#else
grammar java_libbash;
#endif
options
{
output = AST;
#ifdef OUTPUT_C
language = C;
ASTLabelType = pANTLR3_BASE_TREE;
#else
language = Java;
ASTLabelType = CommonTree;
#endif
}
tokens{
ANSI_C_QUOTING;
ARG;
ARRAY;
ARRAY_SIZE;
BRACE_EXP;
COMMAND_SUB;
CASE_PATTERN;
CASE_COMMAND;
SUBSHELL;
CURRENT_SHELL;
COMPOUND_COND;
CFOR;
FOR_INIT;
FOR_COND;
FOR_MOD;
IF_STATEMENT;
OP;
PRE_INCR;
PRE_DECR;
POST_INCR;
POST_DECR;
PROCESS_SUBSTITUTION;
VAR_REF;
NEGATION;
LIST;
STRING;
COMMAND;
FILE_DESCRIPTOR;
FILE_DESCRIPTOR_MOVE;
REDIR;
ARITHMETIC_CONDITION;
ARITHMETIC_EXPRESSION;
ARITHMETIC;
KEYWORD_TEST;
BUILTIN_TEST;
MATCH_ANY_EXCEPT;
EXTENDED_MATCH_EXACTLY_ONE;
EXTENDED_MATCH_AT_MOST_ONE;
EXTENDED_MATCH_NONE;
EXTENDED_MATCH_ANY;
EXTENDED_MATCH_AT_LEAST_ONE;
BRANCH;
MATCH_PATTERN;
MATCH_REGULAR_EXPRESSION;
ESCAPED_CHAR;
NOT_MATCH_PATTERN;
MATCH_ANY;
MATCH_ANY_EXCEPT;
MATCH_ALL;
MATCH_ONE;
CHARACTER_CLASS;
EQUIVALENCE_CLASS;
COLLATING_SYMBOL;
DOUBLE_QUOTED_STRING;
SINGLE_QUOTED_STRING;
VARIABLE_DEFINITIONS;
USE_DEFAULT_WHEN_UNSET;
USE_ALTERNATE_WHEN_UNSET;
DISPLAY_ERROR_WHEN_UNSET;
ASSIGN_DEFAULT_WHEN_UNSET;
USE_DEFAULT_WHEN_UNSET_OR_NULL;
USE_ALTERNATE_WHEN_UNSET_OR_NULL;
DISPLAY_ERROR_WHEN_UNSET_OR_NULL;
ASSIGN_DEFAULT_WHEN_UNSET_OR_NULL;
OFFSET;
LIST_EXPAND;
REPLACE_FIRST;
REPLACE_ALL;
REPLACE_AT_START;
REPLACE_AT_END;
LAZY_REMOVE_AT_START;
LAZY_REMOVE_AT_END;
EMPTY_EXPANSION_VALUE;
PLUS_SIGN;
MINUS_SIGN;
PLUS_ASSIGN;
MINUS_ASSIGN;
DIVIDE_ASSIGN;
MUL_ASSIGN;
MOD_ASSIGN;
LSHIFT_ASSIGN;
RSHIFT_ASSIGN;
AND_ASSIGN;
XOR_ASSIGN;
OR_ASSIGN;
LEQ;
GEQ;
NOT_EQUALS;
EQUALS_TO;
BUILTIN_LOGIC_AND;
BUILTIN_LOGIC_OR;
FUNCTION;
}
@lexer::context
{
#ifdef OUTPUT_C
bool double_quoted;
int paren_level;
#endif
}
@lexer::apifuncs
{
#ifdef OUTPUT_C
ctx->double_quoted = false;
ctx->paren_level = 0;
#endif
}
@lexer::members
{
#ifndef OUTPUT_C
boolean double_quoted = false;
int paren_level = 0;
int LA(int index) {
return input.LA(index);
}
#endif
}
#ifdef OUTPUT_C
@includes {
C_INCLUDE #include
C_INCLUDE #include
}
@postinclude {
C_INCLUDE #include
C_INCLUDE #include "exceptions.h"
}
@lexer::postinclude {
#define double_quoted ctx->double_quoted
#define paren_level ctx->paren_level
}
#endif
@members
{
#ifdef OUTPUT_C
static std::string get_string(pANTLR3_COMMON_TOKEN token)
{
if(!token || !token->start)
return "";
// Use reinterpret_cast here because we have to cast C code.
// The real type here is int64_t which is used as a pointer.
// token->stop - token->start + 1 should be bigger than 0.
return std::string(reinterpret_cast(token->start),
boost::numeric_cast(token->stop - token->start + 1));
}
static bool is_here_end(plibbashParser ctx, const std::string& here_document_word, int number_of_tokens_in_word)
{
std::string word;
for(int i = 1; i <= number_of_tokens_in_word; ++i)
word += get_string(LT(i));
return (word == here_document_word);
}
static void free_here_document(plibbashParser_here_document_SCOPE scope)
{
(&(scope->here_document_word))->std::string::~string();
}
#else
boolean is_here_end(String here_document_word, int number_of_tokens) {
String word = "";
for(int i = 1; i <= number_of_tokens; ++i)
word += input.LT(i).getText();
return (word.equals(here_document_word));
}
String get_string(Token token) {
return token.getText();
}
Token LT(int index) {
return input.LT(index);
}
int LA(int index) {
return input.LA(index);
}
#endif
#ifdef OUTPUT_C
bool is_special_token(int token)
#else
boolean is_special_token(int token)
#endif
{
return token == AMP
||token == BLANK
// for bash redirection
||token == LESS_THAN
||token == GREATER_THAN
||token == RSHIFT
||token == AMP_LESS_THAN
||token == AMP_GREATER_THAN
||token == AMP_RSHIFT
// for end of command
||token == SEMIC
||token == EOL
// for sub shell
||token == LPAREN
||token == RPAREN
// for case statement
||token == DOUBLE_SEMIC
// for logical operator
||token == LOGICAND
||token == LOGICOR
// for pipeline
||token == PIPE
// for document and here string
||token == HERE_STRING_OP
||token == LSHIFT;
}
}
start
: ((POUND) =>first_line_comment)? EOL* BLANK? command_list BLANK? (SEMIC|AMP|EOL)? EOF -> command_list;
first_line_comment
: POUND ~(EOL)* EOL;
command_list
: list_level_2 -> ^(LIST list_level_2);
list_level_1
: pipeline (BLANK!?(LOGICAND^|LOGICOR^)(BLANK!|EOL!)* pipeline)*;
list_level_2
: list_level_1 (BLANK!? command_separator (BLANK!? EOL!)* BLANK!? list_level_1)*;
command_separator
: SEMIC!
| AMP^
| EOL!;
pipeline
: time? ((BANG) => (BANG BLANK!))? command^ (BLANK!? PIPE^ BLANK!? command)*;
time
: TIME^ BLANK! ((time_posix) => time_posix)?;
time_posix
: MINUS! LETTER BLANK!;
redirection
: redirection_atom+;
redirection_atom
: redirection_operator redirection_destination -> ^(REDIR redirection_operator redirection_destination)
| BLANK! process_substitution
| here_string;
process_substitution
: (dir=LESS_THAN|dir=GREATER_THAN)LPAREN BLANK* command_list BLANK* RPAREN
-> ^(PROCESS_SUBSTITUTION $dir command_list);
redirection_destination
: (BLANK? file_descriptor) => BLANK!? file_descriptor
| BLANK! process_substitution
| BLANK!? string_expr;
file_descriptor
: DIGIT -> ^(FILE_DESCRIPTOR DIGIT)
| DIGIT MINUS -> ^(FILE_DESCRIPTOR_MOVE DIGIT);
here_string
: BLANK!? HERE_STRING_OP^ BLANK!? (string_expr) => string_expr;
here_document
#ifdef OUTPUT_C
scope {
std::string here_document_word;
int number_of_tokens;
}
@init {
// http://antlr.1301665.n2.nabble.com/C-target-initialization-of-return-scope-structures-td5078478.html
new (&($here_document::here_document_word)) std::string;
$here_document::number_of_tokens = 0;
ctx->plibbashParser_here_documentTop->free = &free_here_document;
}
#else
scope {
String here_document_word;
int number_of_tokens;
}
@init {
$here_document::here_document_word = "";
$here_document::number_of_tokens = 0;
}
#endif
: BLANK? (here_document_operator) => here_document_operator BLANK? here_document_begin
redirection? EOL here_document_content? here_document_end
-> ^(here_document_operator ^(STRING here_document_content?) redirection?);
here_document_operator
: LSHIFT
(
(MINUS) => MINUS -> OP["<<-"]
| -> OP["<<"]
);
here_document_begin
: (
token=~(EOL|BLANK|LESS_THAN|HERE_STRING_OP|GREATER_THAN|RSHIFT|AMP_LESS_THAN|AMP_GREATER_THAN|AMP_RSHIFT)
{
if(LA(-1) != DQUOTE && LA(-1) != ESC)
{
$here_document::here_document_word += get_string($token);
$here_document::number_of_tokens++;
}
}
)+;
here_document_end
: ({ $here_document::number_of_tokens != 0 }? => . { $here_document::number_of_tokens--; })+;
here_document_content
#ifdef OUTPUT_C
: ({ !is_here_end(ctx, $here_document::here_document_word, $here_document::number_of_tokens)}? => .)+;
#else
: ({ !is_here_end($here_document::here_document_word, $here_document::number_of_tokens)}? => .)+;
#endif
redirection_operator
: BLANK! DIGIT redirection_operator
| BLANK?
(
AMP_LESS_THAN -> OP["&<"]
| GREATER_THAN AMP -> OP[">&"]
| LESS_THAN AMP -> OP["<&"]
| LESS_THAN GREATER_THAN -> OP["<>"]
| RSHIFT -> OP[">>"]
| AMP_GREATER_THAN -> OP["&>"]
| AMP_RSHIFT -> OP ["&>>"]
| LESS_THAN -> LESS_THAN
| GREATER_THAN -> GREATER_THAN
);
command
: command_atom
(
redirection here_document? -> ^(COMMAND redirection command_atom here_document?)
| here_document -> ^(COMMAND command_atom here_document)
| -> ^(COMMAND command_atom)
);
command_atom
: {LA(1) == FOR|| LA(1) == SELECT|| LA(1) == IF|| LA(1) == WHILE|| LA(1) == UNTIL||
LA(1) == CASE|| LA(1) == LPAREN|| LA(1) == LBRACE|| LA(1) == LLPAREN|| LA(1) == LSQUARE||
#ifdef OUTPUT_C
(LA(1) == NAME && LA(2) == BLANK && "test" == get_string(LT(1)))}? => compound_command
| {LA(1) == NAME && LA(2) == BLANK && "function" == get_string(LT(1))}? =>
#else
(LA(1) == NAME && LA(2) == BLANK && "test".equals(get_string(LT(1))))}? => compound_command
| {LA(1) == NAME && LA(2) == BLANK && "function".equals(get_string(LT(1)))}? =>
#endif
NAME BLANK string_expr_no_reserved_word ((BLANK? parens wspace?)|wspace) compound_command
-> ^(FUNCTION string_expr_no_reserved_word compound_command)
| (name (LSQUARE|EQUALS|PLUS EQUALS)) => variable_definitions
(
(BLANK bash_command) => BLANK bash_command -> bash_command variable_definitions
| -> ^(VARIABLE_DEFINITIONS variable_definitions)
)
| (EXPORT) => EXPORT BLANK builtin_variable_definition_item
-> ^(STRING EXPORT) ^(STRING builtin_variable_definition_item)
| (LOCAL) => LOCAL BLANK builtin_variable_definition_item
-> ^(STRING LOCAL) ^(STRING builtin_variable_definition_item)
| (DECLARE) => DECLARE BLANK builtin_variable_definition_item
-> ^(STRING DECLARE) ^(STRING builtin_variable_definition_item)
| command_name
(
(BLANK? parens) => BLANK? parens wspace? compound_command
-> ^(FUNCTION command_name compound_command)
| (
{LA(1) == BLANK &&
(
!is_special_token(LA(2))
// redirection
&&(LA(2) != DIGIT || (LA(3) != AMP_LESS_THAN &&
LA(3) != AMP_GREATER_THAN &&
LA(3) != AMP_RSHIFT &&
LA(3) != GREATER_THAN &&
LA(3) != LESS_THAN &&
LA(3) != RSHIFT))
)}? => BLANK bash_command_arguments
)* -> command_name bash_command_arguments*
);
command_name
: string_expr_no_reserved_word
| {LA(1) == GREATER_THAN}? => redirection_atom -> redirection_atom ^(STRING NAME);
variable_definitions
: variable_definition_atom ((BLANK name (LSQUARE|EQUALS|PLUS EQUALS)) => BLANK! variable_definition_atom)* ;
variable_definition_atom
: name LSQUARE BLANK? explicit_arithmetic BLANK? RSQUARE EQUALS string_expr?
-> ^(EQUALS ^(name explicit_arithmetic) string_expr?)
| name EQUALS value? -> ^(EQUALS name value?)
| name PLUS EQUALS array_value -> ^(PLUS_ASSIGN name array_value)
| name PLUS EQUALS string_expr_part?
-> ^(EQUALS name ^(STRING ^(VAR_REF name) string_expr_part?));
value
: string_expr
| array_value;
array_value
scope {
#ifdef OUTPUT_C
bool array_value_end;
#else
boolean array_value_end;
#endif
}
: LPAREN wspace?
(
RPAREN -> ^(ARRAY)
| {$array_value::array_value_end = false; } array_atom
({!$array_value::array_value_end}? => wspace array_atom)*
-> ^(ARRAY array_atom+)
);
array_atom
: (
(LSQUARE) => LSQUARE! BLANK!? explicit_arithmetic BLANK!? RSQUARE! EQUALS^ string_expr
| string_expr
)
(
(wspace RPAREN) => wspace! RPAREN! {$array_value::array_value_end = true; }
| (RPAREN) => RPAREN! {$array_value::array_value_end = true; }
|
);
builtin_variable_definition_item
scope {
int parens;
}
@init {
$builtin_variable_definition_item::parens = 0;
}
: (
(LPAREN) => LPAREN { ++$builtin_variable_definition_item::parens; }
|(RPAREN) => RPAREN { --$builtin_variable_definition_item::parens; }
|(~(EOL|SEMIC)) => expansion_base
| {LA(1) == EOL && $builtin_variable_definition_item::parens > 0}? => EOL
)+;
#ifdef OUTPUT_C
builtin_variable_definitions[bool local]
: {$local}? => (builtin_variable_definition_atom) (BLANK builtin_variable_definition_atom)*
-> ^(LIST ^(COMMAND ^(VARIABLE_DEFINITIONS LOCAL builtin_variable_definition_atom+)))
| {!$local}? => (builtin_variable_definition_atom) (BLANK builtin_variable_definition_atom)*
-> ^(LIST ^(COMMAND ^(VARIABLE_DEFINITIONS builtin_variable_definition_atom+)));
#else
builtin_variable_definitions
: (builtin_variable_definition_atom) (BLANK builtin_variable_definition_atom)*
-> ^(LIST ^(COMMAND ^(VARIABLE_DEFINITIONS builtin_variable_definition_atom+)));
#endif
builtin_variable_definition_atom
: variable_definition_atom
// We completely ignore the options for export, local and readonly for now
| (MINUS LETTER BLANK) => MINUS LETTER ->
| name -> ^(EQUALS name ^(STRING ^(VAR_REF name)));
bash_command
: string_expr_no_reserved_word ((BLANK bash_command_arguments) => BLANK! bash_command_arguments)*;
bash_command_arguments
: bash_command_argument_atom+ -> ^(STRING bash_command_argument_atom+);
// TODO support brace expansion and braces
bash_command_argument_atom
: (LBRACE) =>
(
(brace_expansion) => brace_expansion
|LBRACE
)
| RBRACE
| string_expr_part;
parens
: LPAREN BLANK? RPAREN;
compound_command
: for_expr
| select_expr
| if_expr
| while_expr
| until_expr
| case_expr
| subshell
| current_shell
| arithmetic_expression
| condition_comparison;
semiel
: BLANK? SEMIC wspace?
| BLANK? EOL wspace?;
for_expr
: FOR BLANK?
(
name
(
wspace IN for_each_value* BLANK? (SEMIC|EOL) wspace?
| wspace? SEMIC wspace?
| wspace
) DO wspace command_list semiel DONE -> ^(FOR name for_each_value* command_list)
| LLPAREN EOL?
// initilization
(BLANK? init=arithmetics BLANK?|BLANK)?
// condition
(SEMIC (BLANK? fcond=arithmetics BLANK?|BLANK)? SEMIC|DOUBLE_SEMIC)
// modification
(BLANK? mod=arithmetics)? wspace? RPAREN RPAREN semiel DO wspace command_list semiel DONE
-> ^(CFOR ^(FOR_INIT $init)? ^(FOR_COND $fcond)? command_list ^(FOR_MOD $mod)?)
);
for_each_value
: {LA(1) == BLANK && LA(2) != EOL && LA(2) != SEMIC && LA(2) != DO}?
=> (BLANK! string_expr);
select_expr
: SELECT BLANK name (wspace IN BLANK string_expr)? semiel DO wspace command_list semiel DONE
-> ^(SELECT name string_expr? command_list) ;
if_expr
: IF wspace ag=command_list semiel THEN wspace iflist=command_list semiel
(elif_expr)*
(ELSE wspace else_list=command_list semiel)? FI
-> ^(IF_STATEMENT ^(IF $ag $iflist) (elif_expr)* ^(ELSE $else_list)?);
elif_expr
: ELIF BLANK ag=command_list semiel THEN wspace iflist=command_list semiel
-> ^(IF["if"] $ag $iflist);
while_expr
: WHILE wspace? istrue=command_list semiel DO wspace dothis=command_list semiel DONE
-> ^(WHILE $istrue $dothis);
until_expr
: UNTIL wspace? istrue=command_list semiel DO wspace dothis=command_list semiel DONE
-> ^(UNTIL $istrue $dothis);
case_expr
: CASE BLANK string_expr wspace IN case_body -> ^(CASE string_expr case_body);
case_body
scope {
#ifdef OUTPUT_C
bool case_end;
#else
boolean case_end;
#endif
}
: {$case_body::case_end = false;}
(
(wspace ESAC) => (wspace ESAC) -> ^(CASE_PATTERN)
|({!$case_body::case_end}? => case_statement)+ -> case_statement+
);
case_statement
: wspace? (LPAREN BLANK?)? extended_pattern (BLANK? PIPE BLANK? extended_pattern)* BLANK? RPAREN
(wspace command_list)?
(
wspace? DOUBLE_SEMIC ((wspace ESAC) => wspace ESAC {$case_body::case_end = true;})?
|wspace ESAC {$case_body::case_end = true;}
)
-> ^(CASE_PATTERN extended_pattern+ (CASE_COMMAND command_list)?);
subshell
: LPAREN wspace? command_list (BLANK? SEMIC)? wspace? RPAREN -> ^(SUBSHELL command_list);
current_shell
: LBRACE wspace command_list semiel RBRACE -> ^(CURRENT_SHELL command_list);
arithmetic_expression
: LLPAREN wspace? arithmetics wspace? RPAREN RPAREN -> ^(ARITHMETIC_EXPRESSION arithmetics);
condition_comparison
: condition_expr -> ^(COMPOUND_COND condition_expr);
condition_expr
: LSQUARE LSQUARE wspace keyword_condition wspace RSQUARE RSQUARE -> ^(KEYWORD_TEST keyword_condition)
| LSQUARE wspace builtin_condition wspace RSQUARE -> ^(BUILTIN_TEST builtin_condition)
#ifdef OUTPUT_C
| {LA(1) == NAME && LA(2) == BLANK && get_string(LT(1)) == "test"}? => NAME wspace? builtin_condition-> ^(BUILTIN_TEST builtin_condition);
#else
| {LA(1) == NAME && LA(2) == BLANK && "test".equals(get_string(LT(1)))}? => NAME wspace? builtin_condition-> ^(BUILTIN_TEST builtin_condition);
#endif
keyword_condition_and
: keyword_condition_primary ( wspace!? LOGICAND^ wspace!? keyword_condition_primary)*;
keyword_condition
: keyword_condition_and ( wspace!? LOGICOR^ wspace!? keyword_condition_and)*;
keyword_negation_primary
: BANG BLANK keyword_condition_primary -> ^(NEGATION keyword_condition_primary);
keyword_condition_primary
: LPAREN! BLANK!? keyword_condition BLANK!? RPAREN!
| (BANG) => keyword_negation_primary
| (unary_operator) => keyword_condition_unary
| keyword_condition_binary;
keyword_condition_unary
: unary_operator^ BLANK! condition_part;
keyword_condition_binary
: condition_part
(
(BLANK EQUALS TILDE) => BLANK EQUALS TILDE BLANK bash_pattern_part
-> ^(MATCH_REGULAR_EXPRESSION condition_part ^(STRING bash_pattern_part))
| keyword_binary_string_operator right=condition_part
-> ^(keyword_binary_string_operator condition_part $right)
| BLANK (BANG EQUALS) BLANK extended_pattern_match+
-> ^(NOT_MATCH_PATTERN condition_part ^(STRING extended_pattern_match+))
| BLANK (EQUALS EQUALS) BLANK extended_pattern_match+
-> ^(MATCH_PATTERN condition_part ^(STRING extended_pattern_match+))
| -> condition_part
);
bash_pattern_part
scope {
int parens;
#ifdef OUTPUT_C
bool quoted;
#else
boolean quoted;
#endif
}
@init {
$bash_pattern_part::parens = 0;
$bash_pattern_part::quoted = false;
}
:(
DQUOTE! { $bash_pattern_part::quoted = !$bash_pattern_part::quoted; }
| {$bash_pattern_part::quoted}? => preserved_tokens
| (ESC BLANK) => ESC BLANK
| LPAREN { if(LA(-2) != ESC) $bash_pattern_part::parens++; }
| LLPAREN { if(LA(-2) != ESC) $bash_pattern_part::parens += 2; }
| {$bash_pattern_part::parens != 0}? => RPAREN { if(LA(-2) != ESC) $bash_pattern_part::parens--; }
| ~(BLANK|EOL|LOGICAND|LOGICOR|LPAREN|RPAREN|DQUOTE|LLPAREN)
)+;
preserved_tokens
: non_dquote -> ESCAPED_CHAR non_dquote;
non_dquote
: ~DQUOTE;
keyword_binary_string_operator
: BLANK! binary_operator BLANK!
| BLANK! EQUALS BLANK!
| BLANK!? LESS_THAN BLANK!?
| BLANK!? GREATER_THAN BLANK!?;
builtin_condition_and
: builtin_condition_primary (builtin_logic_and^ BLANK! builtin_condition_primary)*;
builtin_condition
: builtin_condition_and (builtin_logic_or^ BLANK! builtin_condition_and)*;
builtin_negation_primary
: BANG BLANK builtin_condition_primary -> ^(NEGATION builtin_condition_primary);
builtin_condition_primary
: LPAREN! BLANK!? builtin_condition BLANK!? RPAREN!
| (BANG) => builtin_negation_primary
| (unary_operator) => builtin_condition_unary
| builtin_condition_binary;
builtin_condition_unary
: unary_operator^ BLANK! condition_part;
builtin_condition_binary
: condition_part (BLANK! builtin_binary_string_operator^ BLANK! condition_part)?;
builtin_binary_string_operator
: binary_operator
| (EQUALS EQUALS) => EQUALS EQUALS -> EQUALS
| EQUALS
| BANG EQUALS -> NOT_EQUALS
| ESC_LT
| ESC_GT;
builtin_logic_and
#ifdef OUTPUT_C
: {LA(1) == BLANK && LA(2) == MINUS && LA(3) == LETTER && "a" == get_string(LT(3))}?=> BLANK MINUS LETTER -> BUILTIN_LOGIC_AND;
#else
: {LA(1) == BLANK && LA(2) == MINUS && LA(3) == LETTER && "a".equals(get_string(LT(3)))}?=> BLANK MINUS LETTER -> BUILTIN_LOGIC_AND;
#endif
builtin_logic_or
#ifdef OUTPUT_C
: {LA(1) == BLANK && LA(2) == MINUS && LA(3) == LETTER && "o" == get_string(LT(3))}?=> BLANK MINUS LETTER -> BUILTIN_LOGIC_OR;
#else
: {LA(1) == BLANK && LA(2) == MINUS && LA(3) == LETTER && "o".equals(get_string(LT(3)))}?=> BLANK MINUS LETTER -> BUILTIN_LOGIC_OR;
#endif
binary_operator
: MINUS! NAME^;
unary_operator
: MINUS! LETTER;
// TODO support brace expansion
condition_part
: string_expr;
name
: NAME | LETTER | UNDERSCORE;
num
options{k=1;}
: DIGIT|NUMBER;
string_expr
: (~POUND) => string_expr_part string_expr_part* -> ^(STRING string_expr_part+);
string_expr_part
: quoted_string | non_quoted_string | reserved_word;
string_expr_no_reserved_word
: (~POUND) =>
(
non_quoted_string string_expr_part* -> ^(STRING non_quoted_string string_expr_part*)
| quoted_string string_expr_part* -> ^(STRING quoted_string string_expr_part*)
);
reserved_word
: CASE|DO|DONE|ELIF|ELSE|ESAC|FI|FOR|IF|IN|SELECT|THEN|UNTIL|WHILE|TIME;
non_quoted_string
: string_part
| variable_reference
| command_substitution
| arithmetic_expansion
| brace_expansion
| BANG
| DOLLAR SINGLE_QUOTED_STRING_TOKEN -> ^(ANSI_C_QUOTING SINGLE_QUOTED_STRING_TOKEN);
quoted_string
: double_quoted_string
| SINGLE_QUOTED_STRING_TOKEN -> ^(SINGLE_QUOTED_STRING SINGLE_QUOTED_STRING_TOKEN);
double_quoted_string
: DQUOTE ((~DQUOTE) => expansion_base)* DQUOTE -> ^(DOUBLE_QUOTED_STRING expansion_base*);
// Perform all kinds of expansions
expansion_base
: (DOLLAR (LBRACE|name|num|TIMES|AT|POUND|QMARK|MINUS|DOLLAR|BANG)) => variable_reference
| (command_substitution) => command_substitution
| (DOLLAR (LLPAREN|LSQUARE)) => arithmetic_expansion
| (ESC DQUOTE) => ESC DQUOTE -> DQUOTE
| (ESC TICK) => ESC TICK -> TICK
| (ESC DOLLAR) => ESC DOLLAR -> DOLLAR
| (brace_expansion) => brace_expansion
| (DOLLAR SINGLE_QUOTED_STRING_TOKEN) => DOLLAR SINGLE_QUOTED_STRING_TOKEN
-> ^(ANSI_C_QUOTING SINGLE_QUOTED_STRING_TOKEN)
| .;
all_expansions
: expansion_atom+ -> ^(STRING expansion_atom+);
expansion_atom
: (DQUOTE) => double_quoted_string
| expansion_base;
string_part
: ns_string_part
| SLASH;
ns_string_part
: num|name|escaped_character
|OTHER|EQUALS|PCT|PCTPCT|PLUS|MINUS|DOT|DOTDOT|COLON
|TILDE|LSQUARE|RSQUARE|CARET|POUND|COMMA|EXPORT|LOCAL|DECLARE|AT
// Escaped characters
|ESC_RPAREN|ESC_LPAREN|ESC_RSQUARE|ESC_LSQUARE|ESC_DOLLAR|ESC_GT|ESC_LT|ESC_TICK|ESC_DQUOTE|ESC_SQUOTE
// The following is for filename expansion
|TIMES|QMARK;
escaped_character
: ESC
(
(DIGIT) => DIGIT
| (DIGIT DIGIT) => DIGIT DIGIT
| (DIGIT DIGIT DIGIT) => DIGIT DIGIT DIGIT
| LETTER ALPHANUM ALPHANUM?
| .
);
extended_pattern_match
: (QMARK LPAREN) => QMARK LPAREN extended_pattern (PIPE extended_pattern)* RPAREN
-> ^(EXTENDED_MATCH_AT_MOST_ONE extended_pattern+)
| (TIMES LPAREN) => TIMES LPAREN extended_pattern (PIPE extended_pattern)* RPAREN
-> ^(EXTENDED_MATCH_ANY extended_pattern+)
| (PLUS LPAREN) => PLUS LPAREN extended_pattern (PIPE extended_pattern)* RPAREN
-> ^(EXTENDED_MATCH_AT_LEAST_ONE extended_pattern+)
| (AT LPAREN) => AT LPAREN extended_pattern (PIPE extended_pattern)* RPAREN
-> ^(EXTENDED_MATCH_EXACTLY_ONE extended_pattern+)
| (BANG LPAREN) => BANG LPAREN extended_pattern (PIPE extended_pattern)* RPAREN
-> ^(EXTENDED_MATCH_NONE extended_pattern+)
| (bracket_pattern_match) => bracket_pattern_match
| (pattern_class_match) => pattern_class_match
| string_expr_part;
extended_pattern
: ((~(RPAREN|PIPE)) => extended_pattern_match)+ -> ^(BRANCH extended_pattern_match+);
bracket_pattern_match
: LSQUARE! bracket_pattern_match_operator^ bracket_pattern RSQUARE!
| TIMES -> MATCH_ALL
| QMARK -> MATCH_ONE;
bracket_pattern_match_operator
: (BANG) => BANG -> MATCH_ANY_EXCEPT
| (CARET) => CARET -> MATCH_ANY_EXCEPT
| -> MATCH_ANY;
bracket_pattern_part
: (pattern_class_match) => pattern_class_match
| string_expr_part;
bracket_pattern
: ((~RSQUARE) => bracket_pattern_part)+;
pattern_class_match
: LSQUARE COLON NAME COLON RSQUARE -> ^(CHARACTER_CLASS NAME)
| LSQUARE EQUALS pattern_char EQUALS RSQUARE -> ^(EQUIVALENCE_CLASS pattern_char)
| LSQUARE DOT NAME DOT RSQUARE -> ^(COLLATING_SYMBOL NAME);
pattern_char
: LETTER|DIGIT|OTHER|QMARK|COLON|AT|SEMIC|POUND|SLASH
|BANG|TIMES|COMMA|PIPE|AMP|MINUS|PLUS|PCT|LSQUARE|RSQUARE
|RPAREN|LPAREN|RBRACE|LBRACE|DOLLAR|TICK|DOT|LESS_THAN
|GREATER_THAN|SQUOTE|DQUOTE|AMP_LESS_THAN|AMP_GREATER_THAN|AMP_RSHIFT;
variable_reference
: DOLLAR LBRACE parameter_expansion RBRACE -> ^(VAR_REF parameter_expansion)
| DOLLAR name -> ^(VAR_REF name)
| DOLLAR num -> ^(VAR_REF num)
| DOLLAR TIMES -> ^(VAR_REF TIMES)
| DOLLAR AT -> ^(VAR_REF AT)
| DOLLAR POUND -> ^(VAR_REF POUND)
| DOLLAR QMARK -> ^(VAR_REF QMARK)
| DOLLAR MINUS -> ^(VAR_REF MINUS)
| DOLLAR DOLLAR -> ^(VAR_REF DOLLAR)
| DOLLAR BANG -> ^(VAR_REF BANG);
parameter_expansion
: variable_name
(
(parameter_value_operator) => parameter_value_operator parameter_expansion_value
-> ^(parameter_value_operator variable_name parameter_expansion_value)
| COLON BLANK?
(
os=explicit_arithmetic (COLON BLANK? len=explicit_arithmetic)?
// It will make the tree parser's work easier if OFFSET is used as the root of arithmetic.
// Otherwise, the tree parser can see several arithmetic expressions but can not tell
// which one is for offset and which one is for length.
-> ^(OFFSET variable_name ^(OFFSET $os) ^(OFFSET ^($len))?)
| COLON BLANK? len=explicit_arithmetic
-> ^(OFFSET variable_name ^(OFFSET NUMBER["0"]) ^(OFFSET ^($len))?)
)
| parameter_delete_operator parameter_delete_pattern
-> ^(parameter_delete_operator variable_name parameter_delete_pattern)
| parameter_replace_operator parameter_replace_pattern (SLASH parameter_expansion_value)?
-> ^(parameter_replace_operator variable_name parameter_replace_pattern parameter_expansion_value?)
| BLANK? -> variable_name
)
| BANG variable_name_for_bang
(
TIMES -> ^(BANG variable_name_for_bang TIMES)
| AT -> ^(BANG variable_name_for_bang AT)
| LSQUARE (op=TIMES|op=AT) RSQUARE -> ^(LIST_EXPAND variable_name_for_bang $op)
)
| {LA(1) == POUND && LA(2) != RBRACE }? => variable_size_ref;
parameter_delete_operator
: (POUND POUND) => POUND POUND -> REPLACE_AT_START
| POUND -> LAZY_REMOVE_AT_START
| PCT -> LAZY_REMOVE_AT_END
| PCTPCT -> REPLACE_AT_END;
parameter_value_operator
: COLON MINUS -> USE_DEFAULT_WHEN_UNSET_OR_NULL
| COLON EQUALS -> ASSIGN_DEFAULT_WHEN_UNSET_OR_NULL
| COLON QMARK -> DISPLAY_ERROR_WHEN_UNSET_OR_NULL
| COLON PLUS -> USE_ALTERNATE_WHEN_UNSET_OR_NULL
| MINUS -> USE_DEFAULT_WHEN_UNSET
| EQUALS -> ASSIGN_DEFAULT_WHEN_UNSET
| QMARK -> DISPLAY_ERROR_WHEN_UNSET
| PLUS -> USE_ALTERNATE_WHEN_UNSET;
parameter_replace_pattern
: (SLASH) => -> ^(STRING NAME) // Create an empty string
| ((~SLASH) => parameter_pattern_part)+ -> ^(STRING parameter_pattern_part+);
parameter_delete_pattern
: parameter_pattern_part+ -> ^(STRING parameter_pattern_part+);
parameter_pattern_part
: extended_pattern_match|{is_special_token(LA(1))}? => .;
// TODO fix this rule
parameter_expansion_value
scope {
int num_of_braces;
}
: parameter_expansion_value_atom -> ^(STRING parameter_expansion_value_atom);
parameter_expansion_value_atom
: (~RBRACE) =>
{$parameter_expansion_value::num_of_braces = 1;}
(
{$parameter_expansion_value::num_of_braces != 0}? => .
{
if(LA(1) == LBRACE && LA(-1) != ESC)
++$parameter_expansion_value::num_of_braces;
else if(LA(1) == RBRACE && LA(-1) != ESC)
--$parameter_expansion_value::num_of_braces;
}
)+
| -> EMPTY_EXPANSION_VALUE;
parameter_replace_operator
: (SLASH SLASH) => SLASH SLASH -> REPLACE_ALL
| (SLASH PCT) => SLASH PCT -> REPLACE_AT_END
| (SLASH POUND) => SLASH POUND -> REPLACE_AT_START
| SLASH -> REPLACE_FIRST;
variable_name
: num
| name LSQUARE AT RSQUARE -> ^(ARRAY name AT)
| name LSQUARE TIMES RSQUARE -> ^(ARRAY name TIMES)
| BANG variable_name_for_bang -> ^(VAR_REF variable_name_for_bang)
| variable_name_no_digit
| DOLLAR
| TIMES
| AT
| QMARK
| POUND;
variable_name_no_digit
: name LSQUARE BLANK? explicit_arithmetic BLANK? RSQUARE -> ^(name explicit_arithmetic)
| name;
variable_name_for_bang
: num|name|POUND;
variable_size_ref
: (POUND name LSQUARE) => POUND name LSQUARE array_size_index RSQUARE -> ^(POUND ^(name array_size_index))
| POUND^ variable_name;
array_size_index
: DIGIT+
| (AT|TIMES) -> ARRAY_SIZE;
wspace
: (BLANK|EOL)+;
command_substitution
: COMMAND_SUBSTITUTION_PAREN -> ^(COMMAND_SUB COMMAND_SUBSTITUTION_PAREN)
| COMMAND_SUBSTITUTION_TICK -> ^(COMMAND_SUB COMMAND_SUBSTITUTION_TICK);
brace_expansion
: LBRACE BLANK* brace_expansion_inside BLANK* RBRACE -> ^(BRACE_EXP brace_expansion_inside);
brace_expansion_inside
: commasep|range;
range
: DIGIT DOTDOT^ DIGIT
| LETTER DOTDOT^ LETTER;
brace_expansion_part
: ((~COMMA) => string_expr_part)* -> ^(STRING string_expr_part*);
commasep
: brace_expansion_part (COMMA! brace_expansion_part)+;
explicit_arithmetic
: (DOLLAR (LLPAREN|LSQUARE)) => arithmetic_part // (the predicate resolves the conflict with the primary rule)
| arithmetics;
arithmetic_expansion
: arithmetic_part -> ^(ARITHMETIC_EXPRESSION arithmetic_part);
arithmetic_part
: DOLLAR LLPAREN BLANK? arithmetics BLANK? RPAREN RPAREN -> arithmetics
| DOLLAR LSQUARE BLANK? arithmetics BLANK? RSQUARE -> arithmetics;
arithmetics
: arithmetic (COMMA BLANK? arithmetic)* -> ^(ARITHMETIC arithmetic)+;
arithmetics_test
: arithmetics EOF!;
arithmetic
:(variable_name_no_digit BLANK? arithmetic_assignment_operator)
=> variable_name_no_digit BLANK!? arithmetic_assignment_operator^ BLANK!? logicor
| (arithmetic_variable_reference BLANK? arithmetic_assignment_operator)
=> arithmetic_variable_reference BLANK!? arithmetic_assignment_operator^ BLANK!? logicor
| cnd=logicor
(
QMARK t=logicor COLON f=logicor -> ^(ARITHMETIC_CONDITION $cnd $t $f)
| -> $cnd
);
arithmetic_assignment_operator
: {LA(1) == EQUALS && LA(2) != EQUALS}? => EQUALS
| TIMES EQUALS -> MUL_ASSIGN
| SLASH EQUALS -> DIVIDE_ASSIGN
| PCT EQUALS -> MOD_ASSIGN
| PLUS EQUALS -> PLUS_ASSIGN
| MINUS EQUALS -> MINUS_ASSIGN
| LSHIFT EQUALS -> LSHIFT_ASSIGN
| RSHIFT EQUALS -> RSHIFT_ASSIGN
| AMP EQUALS -> AND_ASSIGN
| CARET EQUALS -> XOR_ASSIGN
| PIPE EQUALS -> OR_ASSIGN;
arithmetic_variable_reference
: variable_reference -> ^(VAR_REF variable_reference);
primary
: num
| command_substitution
| variable_name_no_digit -> ^(VAR_REF variable_name_no_digit)
| variable_reference
| arithmetic_expansion
| LPAREN! (arithmetics) RPAREN!;
pre_post_primary
: DQUOTE!? primary DQUOTE!?;
post_inc_dec
: pre_post_primary ((BLANK) => BLANK)?
(
(PLUS PLUS) => BLANK? PLUS PLUS -> ^(POST_INCR pre_post_primary)
| (MINUS MINUS) => BLANK? MINUS MINUS -> ^(POST_DECR pre_post_primary)
| -> pre_post_primary
);
pre_inc_dec
: PLUS PLUS BLANK? pre_post_primary -> ^(PRE_INCR pre_post_primary)
| MINUS MINUS BLANK? pre_post_primary -> ^(PRE_DECR pre_post_primary);
unary_with_operator
: PLUS BLANK? unary -> ^(PLUS_SIGN unary)
| MINUS BLANK? unary -> ^(MINUS_SIGN unary)
| TILDE BLANK? unary -> ^(TILDE unary)
| BANG BLANK? unary -> ^(BANG unary);
unary
: post_inc_dec
| (PLUS PLUS|MINUS MINUS) => pre_inc_dec
| (PLUS|MINUS|TILDE|BANG) => unary_with_operator;
exponential
: unary (EXP^ BLANK!? unary)* ;
times_division_modulus
: exponential ((TIMES^|SLASH^|PCT^) BLANK!? exponential)*;
addsub
: times_division_modulus ((PLUS^|MINUS^) BLANK!? times_division_modulus)*;
shifts
: addsub ((LSHIFT^|RSHIFT^) BLANK!? addsub)*;
compare
: shifts (compare_operator^ BLANK!? shifts)?;
compare_operator
: LESS_THAN EQUALS -> LEQ
| GREATER_THAN EQUALS -> GEQ
| LESS_THAN
| GREATER_THAN
| EQUALS EQUALS -> EQUALS_TO
| BANG EQUALS -> NOT_EQUALS;
bitwiseand
: compare (AMP^ BLANK!? compare)*;
bitwisexor
: bitwiseand (CARET^ BLANK!? bitwiseand)*;
bitwiseor
: bitwisexor (PIPE^ BLANK!? bitwisexor)*;
logicand
: bitwiseor (LOGICAND^ BLANK!? bitwiseor)*;
logicor
: logicand (LOGICOR^ BLANK!? logicand)*;
COMMENT
: { !double_quoted }?=> (BLANK|EOL) '#' ~('\n'|'\r')* {$channel=HIDDEN;}
;
BANG : '!';
CASE : 'case';
DO : 'do';
DONE : 'done';
ELIF : 'elif';
ELSE : 'else';
ESAC : 'esac';
FI : 'fi';
FOR : 'for';
IF : 'if';
IN : 'in';
SELECT : 'select';
THEN : 'then';
UNTIL : 'until';
WHILE : 'while';
LBRACE : '{';
RBRACE : '}';
TIME : 'time';
RPAREN : ')';
LPAREN : '(';
LLPAREN : '((';
LSQUARE : '[';
RSQUARE : ']';
TICK : '`';
DOLLAR : '$';
AT : '@';
DOT : '.';
DOTDOT : '..';
TIMES : '*';
EQUALS : '=';
MINUS : '-';
PLUS : '+';
EXP : '**';
AMP : '&';
CARET : '^';
LESS_THAN : '<';
GREATER_THAN : '>';
LSHIFT : '<<';
RSHIFT : '>>';
AMP_LESS_THAN : '&<';
AMP_GREATER_THAN : '&>';
AMP_RSHIFT : '&>>';
SEMIC : ';';
DOUBLE_SEMIC : ';;';
PIPE : '|';
ESC_DQUOTE : '\\"';
ESC_SQUOTE : { !double_quoted }? => '\\\'';
DQUOTE : '"' { if(LA(-1) != '\\') double_quoted = !double_quoted; };
SQUOTE : { double_quoted }? => '\'';
SINGLE_QUOTED_STRING_TOKEN : { !double_quoted }? => '\'' .* '\'';
COMMA : ',';
BLANK : (' '|'\t')+;
EOL : ('\r'?'\n')+ ;
DIGIT : '0'..'9';
NUMBER : DIGIT DIGIT+;
LETTER : ('a'..'z'|'A'..'Z');
fragment
ALPHANUM : (DIGIT|LETTER);
TILDE : '~';
HERE_STRING_OP : '<<<';
POUND : '#';
PCT : '%';
PCTPCT : '%%';
SLASH : '/';
COLON : ':';
QMARK : '?';
LOCAL : 'local';
EXPORT : 'export';
DECLARE : 'declare';
LOGICAND : '&&';
LOGICOR : '||';
CONTINUE_LINE : (ESC EOL)+{$channel=HIDDEN;};
ESC_RPAREN : ESC RPAREN;
ESC_LPAREN : ESC LPAREN;
ESC_RSQUARE : ESC RSQUARE;
ESC_LSQUARE : ESC LSQUARE;
ESC_DOLLAR : ESC DOLLAR;
ESC_TICK : ESC TICK;
COMMAND_SUBSTITUTION_PAREN
: {LA(1) == '$' && LA(2) == '(' && LA(3) != '('}? =>
(DOLLAR LPAREN ({ paren_level = 1; }
(
ESC_LPAREN
| ESC_RPAREN
| LPAREN { ++paren_level; }
| RPAREN
{
if(--paren_level == 0)
{
#ifdef OUTPUT_C
LEXSTATE->type = _type;
#else
state.type = _type;
state.channel = _channel;
#endif
return;
}
}
| SINGLE_QUOTED_STRING_TOKEN
| .
)+
));
COMMAND_SUBSTITUTION_TICK : TICK (~(TICK))+ TICK;
ESC_LT : ESC'<';
ESC_GT : ESC'>';
ESC : '\\';
UNDERSCORE : '_';
NAME : (LETTER|UNDERSCORE)(ALPHANUM|UNDERSCORE)+;
OTHER : .;