Help us shape the future of Go! Take the 2026 Go Developer Survey

compiler

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: BSD-3-Clause Imports: 10 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func Compile

func Compile(prog *ast.Program) (iseq *bytecode.ISeq, err error)

Compile lowers a Program into the top-level ISeq, treating string literals as UTF-8 (the default source encoding).

func CompileEval

func CompileEval(prog *ast.Program, m Magic, firstLine int) (iseq *bytecode.ISeq, err error)

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

func CompileWithLocals(prog *ast.Program, localNames []string) (iseq *bytecode.ISeq, err error)

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.

func CompileWithMagic

func CompileWithMagic(prog *ast.Program, m Magic) (iseq *bytecode.ISeq, err error)

CompileWithMagic lowers a Program whose source declared the pragmas in m (see MagicComments), so string literals carry the file's encoding and are frozen and interned when it asked for that.

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

func MagicComments(src string) Magic

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL