Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/prism/lex_compat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def deconstruct_keys(keys) # :nodoc:
KEYWORD_DEF: :on_kw,
KEYWORD_DEFINED: :on_kw,
KEYWORD_DO: :on_kw,
KEYWORD_DO_BLOCK: :on_kw,
KEYWORD_DO_LOOP: :on_kw,
KEYWORD_ELSE: :on_kw,
KEYWORD_ELSIF: :on_kw,
Expand Down
1 change: 1 addition & 0 deletions lib/prism/translation/parser/lexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class Lexer # :nodoc:
KEYWORD_DEF: :kDEF,
KEYWORD_DEFINED: :kDEFINED,
KEYWORD_DO: :kDO,
KEYWORD_DO_BLOCK: :kDO_BLOCK,
KEYWORD_DO_LOOP: :kDO_COND,
KEYWORD_END: :kEND,
KEYWORD_END_UPCASE: :klEND,
Expand Down
2 changes: 2 additions & 0 deletions prism/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,8 @@ tokens:
comment: "def"
- name: KEYWORD_DEFINED
comment: "defined?"
- name: KEYWORD_DO_BLOCK
comment: "do keyword for a block attached to a command"
- name: KEYWORD_DO_LOOP
comment: "do keyword for a predicate in a while, until, or for loop"
- name: KEYWORD_END_UPCASE
Expand Down
7 changes: 7 additions & 0 deletions prism/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,13 @@ struct pm_parser {
/** Whether or not we're at the beginning of a command. */
bool command_start;

/**
* Whether or not we're currently parsing the body of an endless method
* definition. In this context, PM_TOKEN_KEYWORD_DO_BLOCK should not be
* consumed by commands (it should bubble up to the outer context).
*/
bool in_endless_def_body;

/** Whether or not we're currently recovering from a syntax error. */
bool recovering;

Expand Down
88 changes: 72 additions & 16 deletions prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -8330,9 +8330,15 @@ lex_identifier(pm_parser_t *parser, bool previous_command_start) {
switch (width) {
case 2:
if (lex_keyword(parser, current_start, "do", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_DO, PM_TOKEN_EOF) != PM_TOKEN_EOF) {
if (parser->enclosure_nesting == parser->lambda_enclosure_nesting) {
return PM_TOKEN_KEYWORD_DO;
}
if (pm_do_loop_stack_p(parser)) {
return PM_TOKEN_KEYWORD_DO_LOOP;
}
if (!pm_accepts_block_stack_p(parser)) {
return PM_TOKEN_KEYWORD_DO_BLOCK;
}
return PM_TOKEN_KEYWORD_DO;
}

Expand Down Expand Up @@ -12497,6 +12503,7 @@ token_begins_expression_p(pm_token_type_t type) {
case PM_TOKEN_EOF:
case PM_TOKEN_LAMBDA_BEGIN:
case PM_TOKEN_KEYWORD_DO:
case PM_TOKEN_KEYWORD_DO_BLOCK:
case PM_TOKEN_KEYWORD_DO_LOOP:
case PM_TOKEN_KEYWORD_END:
case PM_TOKEN_KEYWORD_ELSE:
Expand Down Expand Up @@ -14825,6 +14832,27 @@ parse_block(pm_parser_t *parser, uint16_t depth) {
return pm_block_node_create(parser, &locals, &opening, parameters, statements, &parser->previous);
}

/**
* Attach a do-block (PM_TOKEN_KEYWORD_DO_BLOCK) to a command-style call node.
* The current token must be PM_TOKEN_KEYWORD_DO_BLOCK when this is called.
*/
static void
parse_command_do_block(pm_parser_t *parser, pm_call_node_t *call, uint16_t depth) {
parser_lex(parser);
pm_block_node_t *block = parse_block(parser, (uint16_t) (depth + 1));

if (call->block != NULL) {
pm_parser_err_node(parser, UP(block), PM_ERR_ARGUMENT_BLOCK_MULTI);
if (call->arguments == NULL) {
call->arguments = pm_arguments_node_create(parser);
}
pm_arguments_node_arguments_append(parser->arena, call->arguments, call->block);
}

call->block = UP(block);
PM_NODE_LENGTH_SET_NODE(call, block);
}

/**
* Parse a list of arguments and their surrounding parentheses if they are
* present. It returns true if it found any pieces of arguments (parentheses,
Expand All @@ -14833,6 +14861,7 @@ parse_block(pm_parser_t *parser, uint16_t depth) {
static bool
parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_block, bool accepts_command_call, uint16_t depth) {
bool found = false;
bool parsed_command_args = false;

if (accept1(parser, PM_TOKEN_PARENTHESIS_LEFT)) {
found |= true;
Expand All @@ -14855,6 +14884,7 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept
}
} else if (accepts_command_call && (token_begins_expression_p(parser->current.type) || match3(parser, PM_TOKEN_USTAR, PM_TOKEN_USTAR_STAR, PM_TOKEN_UAMPERSAND)) && !match1(parser, PM_TOKEN_BRACE_LEFT)) {
found |= true;
parsed_command_args = true;
pm_accepts_block_stack_push(parser, false);

// If we get here, then the subsequent token cannot be used as an infix
Expand Down Expand Up @@ -14885,6 +14915,9 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept
} else if (pm_accepts_block_stack_p(parser) && accept1(parser, PM_TOKEN_KEYWORD_DO)) {
found |= true;
block = parse_block(parser, (uint16_t) (depth + 1));
} else if (parsed_command_args && pm_accepts_block_stack_p(parser) && !parser->in_endless_def_body && accept1(parser, PM_TOKEN_KEYWORD_DO_BLOCK)) {
found |= true;
block = parse_block(parser, (uint16_t) (depth + 1));
}

if (block != NULL) {
Expand Down Expand Up @@ -15300,7 +15333,7 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl
#define PM_CASE_KEYWORD PM_TOKEN_KEYWORD___ENCODING__: case PM_TOKEN_KEYWORD___FILE__: case PM_TOKEN_KEYWORD___LINE__: \
case PM_TOKEN_KEYWORD_ALIAS: case PM_TOKEN_KEYWORD_AND: case PM_TOKEN_KEYWORD_BEGIN: case PM_TOKEN_KEYWORD_BEGIN_UPCASE: \
case PM_TOKEN_KEYWORD_BREAK: case PM_TOKEN_KEYWORD_CASE: case PM_TOKEN_KEYWORD_CLASS: case PM_TOKEN_KEYWORD_DEF: \
case PM_TOKEN_KEYWORD_DEFINED: case PM_TOKEN_KEYWORD_DO: case PM_TOKEN_KEYWORD_DO_LOOP: case PM_TOKEN_KEYWORD_ELSE: \
case PM_TOKEN_KEYWORD_DEFINED: case PM_TOKEN_KEYWORD_DO: case PM_TOKEN_KEYWORD_DO_BLOCK: case PM_TOKEN_KEYWORD_DO_LOOP: case PM_TOKEN_KEYWORD_ELSE: \
case PM_TOKEN_KEYWORD_ELSIF: case PM_TOKEN_KEYWORD_END: case PM_TOKEN_KEYWORD_END_UPCASE: case PM_TOKEN_KEYWORD_ENSURE: \
case PM_TOKEN_KEYWORD_FALSE: case PM_TOKEN_KEYWORD_FOR: case PM_TOKEN_KEYWORD_IF: case PM_TOKEN_KEYWORD_IN: \
case PM_TOKEN_KEYWORD_MODULE: case PM_TOKEN_KEYWORD_NEXT: case PM_TOKEN_KEYWORD_NIL: case PM_TOKEN_KEYWORD_NOT: \
Expand Down Expand Up @@ -17486,7 +17519,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
element = UP(pm_keyword_hash_node_create(parser));
pm_static_literals_t hash_keys = { 0 };

if (!match8(parser, PM_TOKEN_EOF, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_KEYWORD_DO, PM_TOKEN_PARENTHESIS_RIGHT)) {
if (!match8(parser, PM_TOKEN_EOF, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_KEYWORD_DO_BLOCK, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_KEYWORD_DO, PM_TOKEN_PARENTHESIS_RIGHT)) {
parse_assocs(parser, &hash_keys, element, (uint16_t) (depth + 1));
}

Expand Down Expand Up @@ -18895,20 +18928,30 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
allow_command_call = binding_power == PM_BINDING_POWER_ASSIGNMENT || binding_power < PM_BINDING_POWER_COMPOSITION;
}

// Inside a def body, we push true onto the
// accepts_block_stack so that `do` is lexed as
// PM_TOKEN_KEYWORD_DO (which can only start a block for
// primary-level constructs, not commands). During command
// argument parsing, the stack is pushed to false, causing
// `do` to be lexed as PM_TOKEN_KEYWORD_DO_BLOCK, which
// is not consumed inside the endless def body and instead
// left for the outer context.
pm_accepts_block_stack_push(parser, true);
bool previous_in_endless_def_body = parser->in_endless_def_body;
parser->in_endless_def_body = true;
pm_node_t *statement = parse_expression(parser, PM_BINDING_POWER_DEFINED + 1, allow_command_call, false, PM_ERR_DEF_ENDLESS, (uint16_t) (depth + 1));
parser->in_endless_def_body = previous_in_endless_def_body;
pm_accepts_block_stack_pop(parser);

// In an endless method definition, the body is not allowed to
// be a command with a do..end block.
if (PM_NODE_TYPE_P(statement, PM_CALL_NODE)) {
pm_call_node_t *call = (pm_call_node_t *) statement;

if (call->arguments != NULL && call->block != NULL && PM_NODE_TYPE_P(call->block, PM_BLOCK_NODE)) {
pm_block_node_t *block = (pm_block_node_t *) call->block;

if (parser->start[block->opening_loc.start] != '{') {
pm_parser_err_node(parser, call->block, PM_ERR_DEF_ENDLESS_DO_BLOCK);
}
}
// If an unconsumed PM_TOKEN_KEYWORD_DO follows the body,
// it is an error (e.g., `def f = 1 do end`).
// PM_TOKEN_KEYWORD_DO_BLOCK is intentionally not caught
// here — it should bubble up to the outer context (e.g.,
// `private def f = puts "Hello" do end` where the block
// attaches to `private`).
if (accept1(parser, PM_TOKEN_KEYWORD_DO)) {
pm_block_node_t *block = parse_block(parser, (uint16_t) (depth + 1));
pm_parser_err_node(parser, UP(block), PM_ERR_DEF_ENDLESS_DO_BLOCK);
}

if (accept1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) {
Expand Down Expand Up @@ -20066,9 +20109,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
opening = parser->previous;

if (!match3(parser, PM_TOKEN_KEYWORD_END, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) {
pm_accepts_block_stack_push(parser, true);
body = UP(parse_statements(parser, PM_CONTEXT_LAMBDA_DO_END, (uint16_t) (depth + 1)));
pm_accepts_block_stack_pop(parser);
}

if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) {
Expand Down Expand Up @@ -21518,6 +21559,14 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc
}
break;
case PM_CALL_NODE:
// A do-block can attach to a command-style call at the
// primary level. Inside an endless def body, DO_BLOCK must
// not be consumed so it can bubble up to the outer context
// (e.g., `private` in `private def f = bar baz do end`).
if (match1(parser, PM_TOKEN_KEYWORD_DO_BLOCK) && !parser->in_endless_def_body && pm_accepts_block_stack_p(parser) && pm_call_node_command_p((pm_call_node_t *) node)) {
parse_command_do_block(parser, (pm_call_node_t *) node, depth);
}

// If we have a call node, then we need to check if it looks like a
// method call without parentheses that contains arguments. If it
// does, then it has different rules for parsing infix operators,
Expand Down Expand Up @@ -21573,6 +21622,13 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc
}
break;
case PM_CALL_NODE:
// A do-block can attach to a command-style call
// produced by infix operators (e.g., dot-calls like
// `obj.method args do end`).
if (match1(parser, PM_TOKEN_KEYWORD_DO_BLOCK) && !parser->in_endless_def_body && pm_accepts_block_stack_p(parser) && pm_call_node_command_p((pm_call_node_t *) node)) {
parse_command_do_block(parser, (pm_call_node_t *) node, depth);
}

// These expressions are also statements, by virtue of the
// right-hand side of the expression (i.e., the last argument to
// the call node) being an implicit array.
Expand Down
2 changes: 2 additions & 0 deletions prism/templates/src/token_type.c.erb
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ pm_token_type_human(pm_token_type_t token_type) {
return "'defined?'";
case PM_TOKEN_KEYWORD_DO:
return "'do'";
case PM_TOKEN_KEYWORD_DO_BLOCK:
return "'do'";
case PM_TOKEN_KEYWORD_DO_LOOP:
return "'do'";
case PM_TOKEN_KEYWORD_ELSE:
Expand Down
5 changes: 4 additions & 1 deletion test/prism/errors/def_endless_do.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
def a = a b do 1 end
^~~~~~~~ unexpected `do` for block in an endless method definition
^~ unexpected 'do', expecting end-of-input
^~ unexpected 'do', ignoring it
^~~ unexpected 'end', expecting end-of-input
^~~ unexpected 'end', ignoring it

3 changes: 3 additions & 0 deletions test/prism/fixtures/4.0/endless_methods_command_call.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ private def foo(x) = puts x
private def obj.foo = puts "Hello"
private def obj.foo() = puts "Hello"
private def obj.foo(x) = puts x

private def foo = bar baz
private def foo = bar baz do expr end
8 changes: 8 additions & 0 deletions test/prism/fixtures/blocks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,11 @@ foo lambda { |
}

foo do |bar,| end

foo bar baz, qux do end

foo.bar baz do end

foo.bar baz do end.qux quux do end

foo bar, baz do |x| x end
4 changes: 4 additions & 0 deletions test/prism/fixtures/endless_methods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ def bar = A ""
def method = 1 + 2 + 3

x = def f = p 1

def foo = bar baz

def foo = bar(baz)
3 changes: 3 additions & 0 deletions zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@ def stats_string
:guard_shape_count,
:guard_shape_exit_ratio,

:load_field_count,
:store_field_count,

:side_exit_size,
:code_region_bytes,
:side_exit_size_ratio,
Expand Down
2 changes: 2 additions & 0 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1210,12 +1210,14 @@ fn gen_load_self() -> Opnd {
}

fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, return_type: Type) -> Opnd {
gen_incr_counter(asm, Counter::load_field_count);
asm_comment!(asm, "Load field id={} offset={}", id.contents_lossy(), offset);
let recv = asm.load(recv);
asm.load(Opnd::mem(return_type.num_bits(), recv, offset))
}

fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Opnd, val_type: Type) {
gen_incr_counter(asm, Counter::store_field_count);
asm_comment!(asm, "Store field id={} offset={}", id.contents_lossy(), offset);
let recv = asm.load(recv);
asm.store(Opnd::mem(val_type.num_bits(), recv, offset), val);
Expand Down
2 changes: 2 additions & 0 deletions zjit/src/cruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ unsafe extern "C" {
pub fn rb_str_setbyte(str: VALUE, index: VALUE, value: VALUE) -> VALUE;
pub fn rb_str_getbyte(str: VALUE, index: VALUE) -> VALUE;
pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE;
pub fn rb_jit_fix_div_fix(x: VALUE, y: VALUE) -> VALUE;
pub fn rb_jit_fix_mod_fix(x: VALUE, y: VALUE) -> VALUE;
pub fn rb_vm_concat_array(ary1: VALUE, ary2st: VALUE) -> VALUE;
pub fn rb_vm_get_special_object(reg_ep: *const VALUE, value_type: vm_special_object_type) -> VALUE;
Expand Down Expand Up @@ -207,6 +208,7 @@ pub use rb_vm_ci_kwarg as vm_ci_kwarg;
pub use rb_METHOD_ENTRY_VISI as METHOD_ENTRY_VISI;
pub use rb_RCLASS_ORIGIN as RCLASS_ORIGIN;
pub use rb_vm_get_special_object as vm_get_special_object;
pub use rb_jit_fix_div_fix as rb_fix_div_fix;
pub use rb_jit_fix_mod_fix as rb_fix_mod_fix;

/// Helper so we can get a Rust string for insn_name()
Expand Down
12 changes: 12 additions & 0 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5080,6 +5080,18 @@ impl Function {
_ => None,
})
}
Insn::FixnumDiv { left, right, .. } => {
self.fold_fixnum_bop(insn_id, left, right, |l, r| match (l, r) {
(Some(l), Some(r)) if l == (RUBY_FIXNUM_MIN as i64) && r == -1 => None, // Avoid Fixnum overflow
(Some(_l), Some(r)) if r == 0 => None, // Avoid Divide by zero.
(Some(l), Some(r)) => {
let l_obj = VALUE::fixnum_from_isize(l as isize);
let r_obj = VALUE::fixnum_from_isize(r as isize);
Some(unsafe { rb_jit_fix_div_fix(l_obj, r_obj) }.as_fixnum())
},
_ => None,
})
}
Insn::FixnumMod { left, right, .. } => {
self.fold_fixnum_bop(insn_id, left, right, |l, r| match (l, r) {
(Some(l), Some(r)) if r != 0 => {
Expand Down
Loading