Documentation
¶
Overview ¶
Package compiler lowers the AST to bytecode (an ISeq tree).
Each method body and the program top level becomes one ISeq. Locals are resolved to flat slot indices here (Phase 0 has no closures, so each ISeq has a single flat local table; depth-addressed envs arrive with blocks in Phase 1, plan §6).
The flip-flop operator: a two-sided range literal written where a condition is expected (`x if (i == 4)..(i == 7)`). It is not a Range object but a piece of state that turns on when the left condition holds and off after the right one does — awk's and sed's range addressing.
MRI's parser marks such a range NODE_FLIP2 (`..`) or NODE_FLIP3 (`...`) by its syntactic position, and compile.c's compile_flip_flop (ruby/ruby v3_4_0:4607) lowers it against a per-occurrence slot allocated in the enclosing *local* iseq — ISEQ_FLIP_CNT_INCREMENT(ISEQ_BODY(iseq)->local_iseq) — so a block shares its state with every other invocation of that block, and two textually identical flip-flops in different iseqs do not interfere.
The generated sequence is MRI's, with one difference: MRI compiles it straight into the surrounding branch's then/else labels, while rbgo's conditions are ordinary values that the caller branches on, so this pushes true or false instead. rbgo's local slots are already depth-addressed and resolution stops at the first non-block scope, which is exactly MRI's local_iseq, so a slot allocated there has the lifetime the operator needs.
Compound assignment to an index (`a[i] op= v`) and to an attribute (`a.x op= v`), lowered so the receiver and the index arguments are evaluated exactly once and the expression's value is the assigned value — never what the `[]=` / `x=` setter happens to return.
The parser desugars both forms textually: `a[i] op= v` arrives as `a.[]=(i, a.[](i) op v)` and `a.x op= v` as `a.x=(a.x op v)`, with the receiver and index nodes SHARED (same AST pointers) between the read and the write. That shape is correct for value but wrong for effect: compiled literally it evaluates the receiver twice and every index argument twice. MRI does not: compile.c's compile_op_asgn1 evaluates the receiver and index once and reuses them with `dupn` (ruby/ruby v3_4_0 compile.c:9401), and compile_op_asgn2 does the same for an attribute with `dup` (compile.c:9535). Both then overwrite the reserved result slot with `setn`, so the expression yields the right-hand value (or, when `||=` / `&&=` short-circuits, the value already there).
rbgo's stack has no `dupn`/`setn`/`topn`, so the same effect is obtained with anonymous local slots as temporaries.
Index ¶
- func Compile(prog *ast.Program) (iseq *bytecode.ISeq, err error)
- func CompileEval(prog *ast.Program, m Magic, firstLine int) (iseq *bytecode.ISeq, err error)
- func CompileEvalWithLocals(prog *ast.Program, m Magic, localNames []string, firstLine int) (iseq *bytecode.ISeq, err error)
- func CompileWithLocals(prog *ast.Program, localNames []string) (iseq *bytecode.ISeq, err error)
- func CompileWithMagic(prog *ast.Program, m Magic) (iseq *bytecode.ISeq, err error)
- type Compiler
- type Magic
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Compile ¶
Compile lowers a Program into the top-level ISeq, treating string literals as UTF-8 (the default source encoding).
func CompileEval ¶
CompileEval lowers prog as a top-level body whose FIRST LINE is firstLine rather than 1 — the shape an eval given an explicit starting line needs (eval(src, nil, file, line), instance_eval(src, file, line), whose line reaches MRI's parser as pm_options_line_set). firstLine may be negative; MRI does not clamp it. CompileWithMagic is this function at firstLine 1.
func CompileEvalWithLocals ¶
func CompileEvalWithLocals(prog *ast.Program, m Magic, localNames []string, firstLine int) (iseq *bytecode.ISeq, err error)
CompileEvalWithLocals is CompileWithLocals for a binding eval given an explicit starting line — eval(src, b, file, line) and Binding#eval(src, file, line). CompileWithLocals is this function at firstLine 1.
func CompileWithLocals ¶
CompileWithLocals lowers prog for a Binding eval: it compiles in a child scope whose parent holds the binding's locals (by slot), so references to them resolve at depth 1 — reads and writes reach the binding's environment when the ISeq is run with that environment as the parent — while any new locals are scratch in the child frame.
Types ¶
type Compiler ¶
type Compiler struct {
// contains filtered or unexported fields
}
Compiler walks the AST, maintaining a stack of builders.
type Magic ¶
type Magic struct {
// Encoding is the canonical registry encoding name a `# encoding:` /
// `# coding:` comment declared, or "" for the UTF-8 default.
Encoding string
// FrozenStringLiteral is true when `# frozen_string_literal: true` was
// declared, making every non-interpolated String literal in the file a
// shared frozen object.
FrozenStringLiteral bool
}
Magic holds what a source file's leading magic-comment block declared. It is what the compiler needs from the source TEXT, which the compiler never sees: it is handed an already-parsed ast.Program, so every such property has to be read by the caller and passed in. Grouping them in one value means the next pragma MRI grows (`# shareable_constant_value:`, `# warn_indent:` — see the magic_comments table, parse.y ruby_4_0:9448) costs no call-site churn.
func MagicComments ¶
MagicComments reads src's leading magic-comment block.
The two pragmas are gated DIFFERENTLY in MRI, which is why they are scanned separately here:
- the encoding comment only counts at the top of the file — magic_comment_encoding returns early unless comment_at_top(p), i.e. line 1, or line 2 after a shebang (parse.y ruby_4_0:9328 and 9340);
- frozen_string_literal counts anywhere before the first TOKEN — parser_set_frozen_string_literal only checks p->token_seen (parse.y ruby_4_0:9380).
So `# encoding: X` on line 2 after a plain comment is ignored while `# frozen_string_literal: true` there is honoured, which is what ruby/spec's "ignores the magic encoding comment if it is after a frozen_string_literal magic comment" (core/kernel/eval_spec.rb) is really observing.