aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Newton <kddnewton@gmail.com>2024-06-10 09:27:54 -0400
committergit <svn-admin@ruby-lang.org>2024-06-10 13:42:40 +0000
commit21e06e57af4ac002840ddf7d875ae3c6a8234d3a (patch)
tree4b893bc06e6293e52d7f5f594a0793a7f5b88e3d
parent528a23b53c07001461e35aad30deabd736bb417c (diff)
downloadruby-21e06e57af4ac002840ddf7d875ae3c6a8234d3a.tar.gz
[ruby/prism] Allow block exits in loop predicates
https://github.com/ruby/prism/commit/f09db18e46
-rw-r--r--prism/parser.h3
-rw-r--r--prism/prism.c236
-rw-r--r--test/prism/fixtures/break.txt4
-rw-r--r--test/prism/snapshots/break.txt156
4 files changed, 167 insertions, 232 deletions
diff --git a/prism/parser.h b/prism/parser.h
index 2bae4b0ac3..c5f8ab9df4 100644
--- a/prism/parser.h
+++ b/prism/parser.h
@@ -364,6 +364,9 @@ typedef enum {
/** a rescue statement within a lambda expression */
PM_CONTEXT_LAMBDA_RESCUE,
+ /** the predicate clause of a loop statement */
+ PM_CONTEXT_LOOP_PREDICATE,
+
/** the top level context */
PM_CONTEXT_MAIN,
diff --git a/prism/prism.c b/prism/prism.c
index c546c62aa0..41381d7312 100644
--- a/prism/prism.c
+++ b/prism/prism.c
@@ -14,180 +14,6 @@ pm_version(void) {
*/
#define PM_TAB_WHITESPACE_SIZE 8
-#ifndef PM_DEBUG_LOGGING
-/**
- * Debugging logging will provide you with additional debugging functions as
- * well as automatically replace some functions with their debugging
- * counterparts.
- */
-#define PM_DEBUG_LOGGING 0
-#endif
-
-#if PM_DEBUG_LOGGING
-
-/******************************************************************************/
-/* Debugging */
-/******************************************************************************/
-
-PRISM_ATTRIBUTE_UNUSED static const char *
-debug_context(pm_context_t context) {
- switch (context) {
- case PM_CONTEXT_BEGIN: return "BEGIN";
- case PM_CONTEXT_BEGIN_ENSURE: return "BEGIN_ENSURE";
- case PM_CONTEXT_BEGIN_ELSE: return "BEGIN_ELSE";
- case PM_CONTEXT_BEGIN_RESCUE: return "BEGIN_RESCUE";
- case PM_CONTEXT_BLOCK_BRACES: return "BLOCK_BRACES";
- case PM_CONTEXT_BLOCK_KEYWORDS: return "BLOCK_KEYWORDS";
- case PM_CONTEXT_BLOCK_ENSURE: return "BLOCK_ENSURE";
- case PM_CONTEXT_BLOCK_ELSE: return "BLOCK_ELSE";
- case PM_CONTEXT_BLOCK_RESCUE: return "BLOCK_RESCUE";
- case PM_CONTEXT_CASE_IN: return "CASE_IN";
- case PM_CONTEXT_CASE_WHEN: return "CASE_WHEN";
- case PM_CONTEXT_CLASS: return "CLASS";
- case PM_CONTEXT_CLASS_ELSE: return "CLASS_ELSE";
- case PM_CONTEXT_CLASS_ENSURE: return "CLASS_ENSURE";
- case PM_CONTEXT_CLASS_RESCUE: return "CLASS_RESCUE";
- case PM_CONTEXT_DEF: return "DEF";
- case PM_CONTEXT_DEF_PARAMS: return "DEF_PARAMS";
- case PM_CONTEXT_DEF_ENSURE: return "DEF_ENSURE";
- case PM_CONTEXT_DEF_ELSE: return "DEF_ELSE";
- case PM_CONTEXT_DEF_RESCUE: return "DEF_RESCUE";
- case PM_CONTEXT_DEFAULT_PARAMS: return "DEFAULT_PARAMS";
- case PM_CONTEXT_DEFINED: return "DEFINED";
- case PM_CONTEXT_ELSE: return "ELSE";
- case PM_CONTEXT_ELSIF: return "ELSIF";
- case PM_CONTEXT_EMBEXPR: return "EMBEXPR";
- case PM_CONTEXT_FOR_INDEX: return "FOR_INDEX";
- case PM_CONTEXT_FOR: return "FOR";
- case PM_CONTEXT_IF: return "IF";
- case PM_CONTEXT_LAMBDA_BRACES: return "LAMBDA_BRACES";
- case PM_CONTEXT_LAMBDA_DO_END: return "LAMBDA_DO_END";
- case PM_CONTEXT_LAMBDA_ENSURE: return "LAMBDA_ENSURE";
- case PM_CONTEXT_LAMBDA_ELSE: return "LAMBDA_ELSE";
- case PM_CONTEXT_LAMBDA_RESCUE: return "LAMBDA_RESCUE";
- case PM_CONTEXT_MAIN: return "MAIN";
- case PM_CONTEXT_MODULE: return "MODULE";
- case PM_CONTEXT_MODULE_ELSE: return "MODULE_ELSE";
- case PM_CONTEXT_MODULE_ENSURE: return "MODULE_ENSURE";
- case PM_CONTEXT_MODULE_RESCUE: return "MODULE_RESCUE";
- case PM_CONTEXT_NONE: return "NONE";
- case PM_CONTEXT_PARENS: return "PARENS";
- case PM_CONTEXT_POSTEXE: return "POSTEXE";
- case PM_CONTEXT_PREDICATE: return "PREDICATE";
- case PM_CONTEXT_PREEXE: return "PREEXE";
- case PM_CONTEXT_RESCUE_MODIFIER: return "RESCUE_MODIFIER";
- case PM_CONTEXT_SCLASS: return "SCLASS";
- case PM_CONTEXT_SCLASS_ENSURE: return "SCLASS_ENSURE";
- case PM_CONTEXT_SCLASS_ELSE: return "SCLASS_ELSE";
- case PM_CONTEXT_SCLASS_RESCUE: return "SCLASS_RESCUE";
- case PM_CONTEXT_TERNARY: return "TERNARY";
- case PM_CONTEXT_UNLESS: return "UNLESS";
- case PM_CONTEXT_UNTIL: return "UNTIL";
- case PM_CONTEXT_WHILE: return "WHILE";
- }
- return NULL;
-}
-
-PRISM_ATTRIBUTE_UNUSED static void
-debug_contexts(pm_parser_t *parser) {
- pm_context_node_t *context_node = parser->current_context;
- fprintf(stderr, "CONTEXTS: ");
-
- if (context_node != NULL) {
- while (context_node != NULL) {
- fprintf(stderr, "%s", debug_context(context_node->context));
- context_node = context_node->prev;
- if (context_node != NULL) {
- fprintf(stderr, " <- ");
- }
- }
- } else {
- fprintf(stderr, "NONE");
- }
-
- fprintf(stderr, "\n");
-}
-
-PRISM_ATTRIBUTE_UNUSED static void
-debug_node(const pm_parser_t *parser, const pm_node_t *node) {
- pm_buffer_t output_buffer = { 0 };
- pm_prettyprint(&output_buffer, parser, node);
-
- fprintf(stderr, "%.*s", (int) output_buffer.length, output_buffer.value);
- pm_buffer_free(&output_buffer);
-}
-
-PRISM_ATTRIBUTE_UNUSED static void
-debug_lex_mode(pm_parser_t *parser) {
- pm_lex_mode_t *lex_mode = parser->lex_modes.current;
- bool first = true;
-
- while (lex_mode != NULL) {
- if (first) {
- first = false;
- } else {
- fprintf(stderr, " <- ");
- }
-
- switch (lex_mode->mode) {
- case PM_LEX_DEFAULT: fprintf(stderr, "DEFAULT"); break;
- case PM_LEX_EMBEXPR: fprintf(stderr, "EMBEXPR"); break;
- case PM_LEX_EMBVAR: fprintf(stderr, "EMBVAR"); break;
- case PM_LEX_HEREDOC: fprintf(stderr, "HEREDOC"); break;
- case PM_LEX_LIST: fprintf(stderr, "LIST (terminator=%c, interpolation=%d)", lex_mode->as.list.terminator, lex_mode->as.list.interpolation); break;
- case PM_LEX_REGEXP: fprintf(stderr, "REGEXP (terminator=%c)", lex_mode->as.regexp.terminator); break;
- case PM_LEX_STRING: fprintf(stderr, "STRING (terminator=%c, interpolation=%d)", lex_mode->as.string.terminator, lex_mode->as.string.interpolation); break;
- }
-
- lex_mode = lex_mode->prev;
- }
-
- fprintf(stderr, "\n");
-}
-
-PRISM_ATTRIBUTE_UNUSED static void
-debug_state(pm_parser_t *parser) {
- fprintf(stderr, "STATE: ");
- bool first = true;
-
- if (parser->lex_state == PM_LEX_STATE_NONE) {
- fprintf(stderr, "NONE\n");
- return;
- }
-
-#define CHECK_STATE(state) \
- if (parser->lex_state & state) { \
- if (!first) fprintf(stderr, "|"); \
- fprintf(stderr, "%s", #state); \
- first = false; \
- }
-
- CHECK_STATE(PM_LEX_STATE_BEG)
- CHECK_STATE(PM_LEX_STATE_END)
- CHECK_STATE(PM_LEX_STATE_ENDARG)
- CHECK_STATE(PM_LEX_STATE_ENDFN)
- CHECK_STATE(PM_LEX_STATE_ARG)
- CHECK_STATE(PM_LEX_STATE_CMDARG)
- CHECK_STATE(PM_LEX_STATE_MID)
- CHECK_STATE(PM_LEX_STATE_FNAME)
- CHECK_STATE(PM_LEX_STATE_DOT)
- CHECK_STATE(PM_LEX_STATE_CLASS)
- CHECK_STATE(PM_LEX_STATE_LABEL)
- CHECK_STATE(PM_LEX_STATE_LABELED)
- CHECK_STATE(PM_LEX_STATE_FITEM)
-
-#undef CHECK_STATE
-
- fprintf(stderr, "\n");
-}
-
-PRISM_ATTRIBUTE_UNUSED static void
-debug_token(pm_token_t * token) {
- fprintf(stderr, "%s: \"%.*s\"\n", pm_token_type_human(token->type), (int) (token->end - token->start), token->start);
-}
-
-#endif
-
// Macros for min/max.
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
@@ -491,8 +317,52 @@ lex_state_set(pm_parser_t *parser, pm_lex_state_t state) {
parser->lex_state = state;
}
+#ifndef PM_DEBUG_LOGGING
+/**
+ * Debugging logging will print additional information to stdout whenever the
+ * lexer state changes.
+ */
+#define PM_DEBUG_LOGGING 0
+#endif
+
#if PM_DEBUG_LOGGING
-static inline void
+PRISM_ATTRIBUTE_UNUSED static void
+debug_state(pm_parser_t *parser) {
+ fprintf(stderr, "STATE: ");
+ bool first = true;
+
+ if (parser->lex_state == PM_LEX_STATE_NONE) {
+ fprintf(stderr, "NONE\n");
+ return;
+ }
+
+#define CHECK_STATE(state) \
+ if (parser->lex_state & state) { \
+ if (!first) fprintf(stderr, "|"); \
+ fprintf(stderr, "%s", #state); \
+ first = false; \
+ }
+
+ CHECK_STATE(PM_LEX_STATE_BEG)
+ CHECK_STATE(PM_LEX_STATE_END)
+ CHECK_STATE(PM_LEX_STATE_ENDARG)
+ CHECK_STATE(PM_LEX_STATE_ENDFN)
+ CHECK_STATE(PM_LEX_STATE_ARG)
+ CHECK_STATE(PM_LEX_STATE_CMDARG)
+ CHECK_STATE(PM_LEX_STATE_MID)
+ CHECK_STATE(PM_LEX_STATE_FNAME)
+ CHECK_STATE(PM_LEX_STATE_DOT)
+ CHECK_STATE(PM_LEX_STATE_CLASS)
+ CHECK_STATE(PM_LEX_STATE_LABEL)
+ CHECK_STATE(PM_LEX_STATE_LABELED)
+ CHECK_STATE(PM_LEX_STATE_FITEM)
+
+#undef CHECK_STATE
+
+ fprintf(stderr, "\n");
+}
+
+static void
debug_lex_state_set(pm_parser_t *parser, pm_lex_state_t state, char const * caller_name, int line_number) {
fprintf(stderr, "Caller: %s:%d\nPrevious: ", caller_name, line_number);
debug_state(parser);
@@ -8492,6 +8362,8 @@ context_terminator(pm_context_t context, pm_token_t *token) {
case PM_CONTEXT_MODULE_ENSURE:
case PM_CONTEXT_SCLASS_ENSURE:
return token->type == PM_TOKEN_KEYWORD_END;
+ case PM_CONTEXT_LOOP_PREDICATE:
+ return token->type == PM_TOKEN_KEYWORD_DO || token->type == PM_TOKEN_KEYWORD_THEN;
case PM_CONTEXT_FOR_INDEX:
return token->type == PM_TOKEN_KEYWORD_IN;
case PM_CONTEXT_CASE_WHEN:
@@ -8664,6 +8536,7 @@ context_human(pm_context_t context) {
case PM_CONTEXT_IF: return "if statement";
case PM_CONTEXT_LAMBDA_BRACES: return "'{'..'}' lambda block";
case PM_CONTEXT_LAMBDA_DO_END: return "'do'..'end' lambda block";
+ case PM_CONTEXT_LOOP_PREDICATE: return "loop predicate";
case PM_CONTEXT_MAIN: return "top level context";
case PM_CONTEXT_MODULE: return "module definition";
case PM_CONTEXT_PARENS: return "parentheses";
@@ -15170,6 +15043,7 @@ parse_block_exit(pm_parser_t *parser, pm_node_t *node, const char *type) {
case PM_CONTEXT_LAMBDA_ELSE:
case PM_CONTEXT_LAMBDA_ENSURE:
case PM_CONTEXT_LAMBDA_RESCUE:
+ case PM_CONTEXT_LOOP_PREDICATE:
case PM_CONTEXT_POSTEXE:
case PM_CONTEXT_UNTIL:
case PM_CONTEXT_WHILE:
@@ -17318,6 +17192,7 @@ parse_retry(pm_parser_t *parser, const pm_node_t *node) {
case PM_CONTEXT_IF:
case PM_CONTEXT_LAMBDA_BRACES:
case PM_CONTEXT_LAMBDA_DO_END:
+ case PM_CONTEXT_LOOP_PREDICATE:
case PM_CONTEXT_PARENS:
case PM_CONTEXT_POSTEXE:
case PM_CONTEXT_PREDICATE:
@@ -17396,6 +17271,7 @@ parse_yield(pm_parser_t *parser, const pm_node_t *node) {
case PM_CONTEXT_LAMBDA_ELSE:
case PM_CONTEXT_LAMBDA_ENSURE:
case PM_CONTEXT_LAMBDA_RESCUE:
+ case PM_CONTEXT_LOOP_PREDICATE:
case PM_CONTEXT_PARENS:
case PM_CONTEXT_POSTEXE:
case PM_CONTEXT_PREDICATE:
@@ -19146,12 +19022,15 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
parser_lex(parser);
return (pm_node_t *) pm_true_node_create(parser, &parser->previous);
case PM_TOKEN_KEYWORD_UNTIL: {
+ context_push(parser, PM_CONTEXT_LOOP_PREDICATE);
pm_do_loop_stack_push(parser, true);
+
parser_lex(parser);
pm_token_t keyword = parser->previous;
-
pm_node_t *predicate = parse_value_expression(parser, PM_BINDING_POWER_COMPOSITION, true, PM_ERR_CONDITIONAL_UNTIL_PREDICATE);
+
pm_do_loop_stack_pop(parser);
+ context_pop(parser);
expect3(parser, PM_TOKEN_KEYWORD_DO_LOOP, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_ERR_CONDITIONAL_UNTIL_PREDICATE);
pm_statements_node_t *statements = NULL;
@@ -19167,12 +19046,15 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
return (pm_node_t *) pm_until_node_create(parser, &keyword, &parser->previous, predicate, statements, 0);
}
case PM_TOKEN_KEYWORD_WHILE: {
+ context_push(parser, PM_CONTEXT_LOOP_PREDICATE);
pm_do_loop_stack_push(parser, true);
+
parser_lex(parser);
pm_token_t keyword = parser->previous;
-
pm_node_t *predicate = parse_value_expression(parser, PM_BINDING_POWER_COMPOSITION, true, PM_ERR_CONDITIONAL_WHILE_PREDICATE);
+
pm_do_loop_stack_pop(parser);
+ context_pop(parser);
expect3(parser, PM_TOKEN_KEYWORD_DO_LOOP, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_ERR_CONDITIONAL_WHILE_PREDICATE);
pm_statements_node_t *statements = NULL;
diff --git a/test/prism/fixtures/break.txt b/test/prism/fixtures/break.txt
index 82fa45bdb4..5532322c5c 100644
--- a/test/prism/fixtures/break.txt
+++ b/test/prism/fixtures/break.txt
@@ -23,3 +23,7 @@ tap { break(1) }
foo { break 42 } == 42
foo { |a| break } == 42
+
+while _ && break; end
+
+until _ && break; end
diff --git a/test/prism/snapshots/break.txt b/test/prism/snapshots/break.txt
index c15a9e4675..7d5bf5e69a 100644
--- a/test/prism/snapshots/break.txt
+++ b/test/prism/snapshots/break.txt
@@ -1,8 +1,8 @@
-@ ProgramNode (location: (1,0)-(25,23))
+@ ProgramNode (location: (1,0)-(29,21))
├── locals: []
└── statements:
- @ StatementsNode (location: (1,0)-(25,23))
- └── body: (length: 11)
+ @ StatementsNode (location: (1,0)-(29,21))
+ └── body: (length: 13)
├── @ CallNode (location: (1,0)-(1,13))
│ ├── flags: ignore_visibility
│ ├── receiver: ∅
@@ -346,56 +346,102 @@
│ │ └── value: 42
│ ├── closing_loc: ∅
│ └── block: ∅
- └── @ CallNode (location: (25,0)-(25,23))
+ ├── @ CallNode (location: (25,0)-(25,23))
+ │ ├── flags: ∅
+ │ ├── receiver:
+ │ │ @ CallNode (location: (25,0)-(25,17))
+ │ │ ├── flags: ignore_visibility
+ │ │ ├── receiver: ∅
+ │ │ ├── call_operator_loc: ∅
+ │ │ ├── name: :foo
+ │ │ ├── message_loc: (25,0)-(25,3) = "foo"
+ │ │ ├── opening_loc: ∅
+ │ │ ├── arguments: ∅
+ │ │ ├── closing_loc: ∅
+ │ │ └── block:
+ │ │ @ BlockNode (location: (25,4)-(25,17))
+ │ │ ├── locals: [:a]
+ │ │ ├── parameters:
+ │ │ │ @ BlockParametersNode (location: (25,6)-(25,9))
+ │ │ │ ├── parameters:
+ │ │ │ │ @ ParametersNode (location: (25,7)-(25,8))
+ │ │ │ │ ├── requireds: (length: 1)
+ │ │ │ │ │ └── @ RequiredParameterNode (location: (25,7)-(25,8))
+ │ │ │ │ │ ├── flags: ∅
+ │ │ │ │ │ └── name: :a
+ │ │ │ │ ├── optionals: (length: 0)
+ │ │ │ │ ├── rest: ∅
+ │ │ │ │ ├── posts: (length: 0)
+ │ │ │ │ ├── keywords: (length: 0)
+ │ │ │ │ ├── keyword_rest: ∅
+ │ │ │ │ └── block: ∅
+ │ │ │ ├── locals: (length: 0)
+ │ │ │ ├── opening_loc: (25,6)-(25,7) = "|"
+ │ │ │ └── closing_loc: (25,8)-(25,9) = "|"
+ │ │ ├── body:
+ │ │ │ @ StatementsNode (location: (25,10)-(25,15))
+ │ │ │ └── body: (length: 1)
+ │ │ │ └── @ BreakNode (location: (25,10)-(25,15))
+ │ │ │ ├── arguments: ∅
+ │ │ │ └── keyword_loc: (25,10)-(25,15) = "break"
+ │ │ ├── opening_loc: (25,4)-(25,5) = "{"
+ │ │ └── closing_loc: (25,16)-(25,17) = "}"
+ │ ├── call_operator_loc: ∅
+ │ ├── name: :==
+ │ ├── message_loc: (25,18)-(25,20) = "=="
+ │ ├── opening_loc: ∅
+ │ ├── arguments:
+ │ │ @ ArgumentsNode (location: (25,21)-(25,23))
+ │ │ ├── flags: ∅
+ │ │ └── arguments: (length: 1)
+ │ │ └── @ IntegerNode (location: (25,21)-(25,23))
+ │ │ ├── flags: decimal
+ │ │ └── value: 42
+ │ ├── closing_loc: ∅
+ │ └── block: ∅
+ ├── @ WhileNode (location: (27,0)-(27,21))
+ │ ├── flags: ∅
+ │ ├── keyword_loc: (27,0)-(27,5) = "while"
+ │ ├── closing_loc: (27,18)-(27,21) = "end"
+ │ ├── predicate:
+ │ │ @ AndNode (location: (27,6)-(27,16))
+ │ │ ├── left:
+ │ │ │ @ CallNode (location: (27,6)-(27,7))
+ │ │ │ ├── flags: variable_call, ignore_visibility
+ │ │ │ ├── receiver: ∅
+ │ │ │ ├── call_operator_loc: ∅
+ │ │ │ ├── name: :_
+ │ │ │ ├── message_loc: (27,6)-(27,7) = "_"
+ │ │ │ ├── opening_loc: ∅
+ │ │ │ ├── arguments: ∅
+ │ │ │ ├── closing_loc: ∅
+ │ │ │ └── block: ∅
+ │ │ ├── right:
+ │ │ │ @ BreakNode (location: (27,11)-(27,16))
+ │ │ │ ├── arguments: ∅
+ │ │ │ └── keyword_loc: (27,11)-(27,16) = "break"
+ │ │ └── operator_loc: (27,8)-(27,10) = "&&"
+ │ └── statements: ∅
+ └── @ UntilNode (location: (29,0)-(29,21))
├── flags: ∅
- ├── receiver:
- │ @ CallNode (location: (25,0)-(25,17))
- │ ├── flags: ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :foo
- │ ├── message_loc: (25,0)-(25,3) = "foo"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (25,4)-(25,17))
- │ ├── locals: [:a]
- │ ├── parameters:
- │ │ @ BlockParametersNode (location: (25,6)-(25,9))
- │ │ ├── parameters:
- │ │ │ @ ParametersNode (location: (25,7)-(25,8))
- │ │ │ ├── requireds: (length: 1)
- │ │ │ │ └── @ RequiredParameterNode (location: (25,7)-(25,8))
- │ │ │ │ ├── flags: ∅
- │ │ │ │ └── name: :a
- │ │ │ ├── optionals: (length: 0)
- │ │ │ ├── rest: ∅
- │ │ │ ├── posts: (length: 0)
- │ │ │ ├── keywords: (length: 0)
- │ │ │ ├── keyword_rest: ∅
- │ │ │ └── block: ∅
- │ │ ├── locals: (length: 0)
- │ │ ├── opening_loc: (25,6)-(25,7) = "|"
- │ │ └── closing_loc: (25,8)-(25,9) = "|"
- │ ├── body:
- │ │ @ StatementsNode (location: (25,10)-(25,15))
- │ │ └── body: (length: 1)
- │ │ └── @ BreakNode (location: (25,10)-(25,15))
- │ │ ├── arguments: ∅
- │ │ └── keyword_loc: (25,10)-(25,15) = "break"
- │ ├── opening_loc: (25,4)-(25,5) = "{"
- │ └── closing_loc: (25,16)-(25,17) = "}"
- ├── call_operator_loc: ∅
- ├── name: :==
- ├── message_loc: (25,18)-(25,20) = "=="
- ├── opening_loc: ∅
- ├── arguments:
- │ @ ArgumentsNode (location: (25,21)-(25,23))
- │ ├── flags: ∅
- │ └── arguments: (length: 1)
- │ └── @ IntegerNode (location: (25,21)-(25,23))
- │ ├── flags: decimal
- │ └── value: 42
- ├── closing_loc: ∅
- └── block: ∅
+ ├── keyword_loc: (29,0)-(29,5) = "until"
+ ├── closing_loc: (29,18)-(29,21) = "end"
+ ├── predicate:
+ │ @ AndNode (location: (29,6)-(29,16))
+ │ ├── left:
+ │ │ @ CallNode (location: (29,6)-(29,7))
+ │ │ ├── flags: variable_call, ignore_visibility
+ │ │ ├── receiver: ∅
+ │ │ ├── call_operator_loc: ∅
+ │ │ ├── name: :_
+ │ │ ├── message_loc: (29,6)-(29,7) = "_"
+ │ │ ├── opening_loc: ∅
+ │ │ ├── arguments: ∅
+ │ │ ├── closing_loc: ∅
+ │ │ └── block: ∅
+ │ ├── right:
+ │ │ @ BreakNode (location: (29,11)-(29,16))
+ │ │ ├── arguments: ∅
+ │ │ └── keyword_loc: (29,11)-(29,16) = "break"
+ │ └── operator_loc: (29,8)-(29,10) = "&&"
+ └── statements: ∅