pub struct Compiler<'source, 'observer> {
contexts: Vec<LambdaCtx>,
warnings: Vec<EvalWarning>,
errors: Vec<Error>,
root_dir: PathBuf,
globals: Rc<GlobalsMap>,
source: &'source SourceCode,
file: &'source File,
observer: &'observer mut dyn CompilerObserver,
dead_scope: usize,
}
Fields§
§contexts: Vec<LambdaCtx>
§warnings: Vec<EvalWarning>
§errors: Vec<Error>
§root_dir: PathBuf
§globals: Rc<GlobalsMap>
Carries all known global tokens; the full set of which is created when the compiler is invoked.
Each global has an associated token, which when encountered as an identifier is resolved against the scope poisoning logic, and a function that should emit code for the token.
source: &'source SourceCode
Reference to the struct holding all of the source code, which is used for error creation.
file: &'source File
File reference in the source map for the current file, which is used for creating spans.
observer: &'observer mut dyn CompilerObserver
Carry an observer for the compilation process, which is called whenever a chunk is emitted.
dead_scope: usize
Carry a count of nested scopes which have requested the compiler not to emit anything. This used for compiling dead code branches to catch errors & warnings in them.
Implementations§
source§impl Compiler<'_, '_>
impl Compiler<'_, '_>
AST-traversing functions related to bindings.
sourcefn compile_plain_inherits<N>(
&mut self,
slot: LocalIdx,
kind: BindingsKind,
count: &mut usize,
node: &N,
) -> Vec<(Expr, SmolStr, Span)>where
N: ToSpan + HasEntryProxy,
fn compile_plain_inherits<N>(
&mut self,
slot: LocalIdx,
kind: BindingsKind,
count: &mut usize,
node: &N,
) -> Vec<(Expr, SmolStr, Span)>where
N: ToSpan + HasEntryProxy,
Compile all inherits of a node with entries that do not have a namespace to inherit from, and return the remaining ones that do.
sourcefn declare_namespaced_inherits(
&mut self,
kind: BindingsKind,
inherit_froms: Vec<(Expr, SmolStr, Span)>,
bindings: &mut TrackedBindings,
)
fn declare_namespaced_inherits( &mut self, kind: BindingsKind, inherit_froms: Vec<(Expr, SmolStr, Span)>, bindings: &mut TrackedBindings, )
Declare all namespaced inherits, that is inherits which are inheriting values from an attribute set.
This only ensures that the locals stack is aware of the inherits, it
does not yet emit bytecode that places them on the stack. This is up to
the owner of the bindings
vector, which this function will populate.
sourcefn declare_bindings<N>(
&mut self,
kind: BindingsKind,
count: &mut usize,
bindings: &mut TrackedBindings,
node: &N,
)where
N: ToSpan + HasEntryProxy,
fn declare_bindings<N>(
&mut self,
kind: BindingsKind,
count: &mut usize,
bindings: &mut TrackedBindings,
node: &N,
)where
N: ToSpan + HasEntryProxy,
Declare all regular bindings (i.e. key = value;
) in a bindings scope,
but do not yet compile their values.
sourcepub(super) fn compile_attr_set(&mut self, slot: LocalIdx, node: &AttrSet)
pub(super) fn compile_attr_set(&mut self, slot: LocalIdx, node: &AttrSet)
Compile attribute set literals into equivalent bytecode.
This is complicated by a number of features specific to Nix attribute sets, most importantly:
- Keys can be dynamically constructed through interpolation.
- Keys can refer to nested attribute sets.
- Attribute sets can (optionally) be recursive.
sourcepub(super) fn compile_env(&mut self, env: &FxHashMap<SmolStr, Value>)
pub(super) fn compile_env(&mut self, env: &FxHashMap<SmolStr, Value>)
Emit definitions for all variables in the top-level global env passed to the evaluation (eg local variables in the REPL)
sourcefn bind_values(&mut self, bindings: TrackedBindings)
fn bind_values(&mut self, bindings: TrackedBindings)
Actually binds all tracked bindings by emitting the bytecode that places them in their stack slots.
fn compile_bindings<N>(&mut self, slot: LocalIdx, kind: BindingsKind, node: &N)where
N: ToSpan + HasEntryProxy,
sourcepub(super) fn compile_let_in(&mut self, slot: LocalIdx, node: &LetIn)
pub(super) fn compile_let_in(&mut self, slot: LocalIdx, node: &LetIn)
Compile a standard let ...; in ...
expression.
Unless in a non-standard scope, the encountered values are simply pushed on the stack and their indices noted in the entries vector.
pub(super) fn compile_legacy_let(&mut self, slot: LocalIdx, node: &LegacyLet)
sourcepub(super) fn is_user_defined(&mut self, ident: &str) -> bool
pub(super) fn is_user_defined(&mut self, ident: &str) -> bool
Is the given identifier defined by the user in any current scope?
sourcefn compile_identifier_access<N: ToSpan + Clone>(
&mut self,
slot: LocalIdx,
ident: &str,
node: &N,
)
fn compile_identifier_access<N: ToSpan + Clone>( &mut self, slot: LocalIdx, ident: &str, node: &N, )
Resolve and compile access to an identifier in the scope.
pub(super) fn compile_ident(&mut self, slot: LocalIdx, node: &Ident)
source§impl Compiler<'_, '_>
impl Compiler<'_, '_>
Private compiler helpers related to bindings.
fn resolve_upvalue(&mut self, ctx_idx: usize, name: &str) -> Option<UpvalueIdx>
fn add_upvalue(&mut self, ctx_idx: usize, kind: UpvalueKind) -> UpvalueIdx
source§impl<'source, 'observer> Compiler<'source, 'observer>
impl<'source, 'observer> Compiler<'source, 'observer>
Compiler construction
pub(crate) fn new( location: Option<PathBuf>, globals: Rc<GlobalsMap>, env: Option<&FxHashMap<SmolStr, Value>>, source: &'source SourceCode, file: &'source File, observer: &'observer mut dyn CompilerObserver, ) -> EvalResult<Self>
source§impl Compiler<'_, '_>
impl Compiler<'_, '_>
fn context(&self) -> &LambdaCtx
fn context_mut(&mut self) -> &mut LambdaCtx
fn chunk(&mut self) -> &mut Chunk
fn scope(&self) -> &Scope
fn scope_mut(&mut self) -> &mut Scope
sourcefn push_op<T: ToSpan>(&mut self, data: Op, node: &T) -> CodeIdx
fn push_op<T: ToSpan>(&mut self, data: Op, node: &T) -> CodeIdx
Push a single instruction to the current bytecode chunk and track the source span from which it was compiled.
fn push_u8(&mut self, data: u8)
fn push_uvarint(&mut self, data: u64)
fn push_u16(&mut self, data: u16)
sourcepub(crate) fn emit_constant<T: ToSpan>(&mut self, value: Value, node: &T)
pub(crate) fn emit_constant<T: ToSpan>(&mut self, value: Value, node: &T)
Emit a single constant to the current bytecode chunk and track the source span from which it was compiled.
source§impl Compiler<'_, '_>
impl Compiler<'_, '_>
fn compile(&mut self, slot: LocalIdx, expr: Expr)
sourcefn compile_dead_code(&mut self, slot: LocalIdx, node: Expr)
fn compile_dead_code(&mut self, slot: LocalIdx, node: Expr)
Compiles an expression, but does not emit any code for it as it is considered dead. This will still catch errors and warnings in that expression.
A warning about the that code being dead is assumed to already be emitted by the caller of this.
fn compile_literal(&mut self, node: &Literal)
fn compile_path(&mut self, slot: LocalIdx, node: &Path)
sourcefn compile_str_parts(
&mut self,
slot: LocalIdx,
parent_node: &Str,
parts: Vec<InterpolPart<String>>,
)
fn compile_str_parts( &mut self, slot: LocalIdx, parent_node: &Str, parts: Vec<InterpolPart<String>>, )
Helper that compiles the given string parts strictly. The caller
(compile_str
) needs to figure out if the result of compiling this
needs to be thunked or not.
fn compile_str(&mut self, slot: LocalIdx, node: &Str)
fn compile_unary_op(&mut self, slot: LocalIdx, op: &UnaryOp)
fn compile_binop(&mut self, slot: LocalIdx, op: &BinOp)
fn compile_and(&mut self, slot: LocalIdx, node: &BinOp)
fn compile_or(&mut self, slot: LocalIdx, node: &BinOp)
fn compile_implication(&mut self, slot: LocalIdx, node: &BinOp)
sourcefn compile_list(&mut self, slot: LocalIdx, node: &List)
fn compile_list(&mut self, slot: LocalIdx, node: &List)
Compile list literals into equivalent bytecode. List construction is fairly simple, consisting of pushing code for each literal element and an instruction with the element count.
The VM, after evaluating the code for each element, simply constructs the list from the given number of elements.
fn compile_attr(&mut self, slot: LocalIdx, node: &Attr)
fn compile_has_attr(&mut self, slot: LocalIdx, node: &HasAttr)
sourcefn optimise_select(&mut self, path: &Attrpath) -> bool
fn optimise_select(&mut self, path: &Attrpath) -> bool
When compiling select or select_or expressions, an optimisation is possible of compiling the set emitted a constant attribute set by immediately replacing it with the actual value.
We take care not to emit an error here, as that would interfere with thunking behaviour (there can be perfectly valid Nix code that accesses a statically known attribute set that is lacking a key, because that thunk is never evaluated). If anything is missing, just inform the caller that the optimisation did not take place and move on. We may want to emit warnings here in the future.
fn compile_select(&mut self, slot: LocalIdx, node: &Select)
sourcefn compile_select_or(
&mut self,
slot: LocalIdx,
set: Expr,
path: Attrpath,
default: Expr,
)
fn compile_select_or( &mut self, slot: LocalIdx, set: Expr, path: Attrpath, default: Expr, )
Compile an or
expression into a chunk of conditional jumps.
If at any point during attribute set traversal a key is
missing, the OpAttrOrNotFound
instruction will leave a
special sentinel value on the stack.
After each access, a conditional jump evaluates the top of the stack and short-circuits to the default value if it sees the sentinel.
Code like { a.b = 1; }.a.c or 42
yields this bytecode and
runtime stack:
Bytecode Runtime stack
┌────────────────────────────┐ ┌─────────────────────────┐
│ ... │ │ ... │
│ 5 OP_ATTRS(1) │ → │ 5 [ { a.b = 1; } ] │
│ 6 OP_CONSTANT("a") │ → │ 6 [ { a.b = 1; } "a" ] │
│ 7 OP_ATTR_OR_NOT_FOUND │ → │ 7 [ { b = 1; } ] │
│ 8 JUMP_IF_NOT_FOUND(13) │ → │ 8 [ { b = 1; } ] │
│ 9 OP_CONSTANT("C") │ → │ 9 [ { b = 1; } "c" ] │
│ 10 OP_ATTR_OR_NOT_FOUND │ → │ 10 [ NOT_FOUND ] │
│ 11 JUMP_IF_NOT_FOUND(13) │ → │ 11 [ ] │
│ 12 JUMP(14) │ │ .. jumped over │
│ 13 CONSTANT(42) │ → │ 12 [ 42 ] │
│ 14 ... │ │ .. .... │
└────────────────────────────┘ └─────────────────────────┘
sourcefn compile_assert(&mut self, slot: LocalIdx, node: &Assert)
fn compile_assert(&mut self, slot: LocalIdx, node: &Assert)
Compile assert
expressions using jumping instructions in the VM.
┌─────────────────────┐
│ 0 [ conditional ] │
│ 1 JUMP_IF_FALSE →┼─┐
│ 2 [ main body ] │ │ Jump to else body if
┌┼─3─← JUMP │ │ condition is false.
Jump over else body ││ 4 OP_ASSERT_FAIL ←┼─┘
if condition is true.└┼─5─→ ... │
└─────────────────────┘
sourcefn compile_if_else(&mut self, slot: LocalIdx, node: &IfElse)
fn compile_if_else(&mut self, slot: LocalIdx, node: &IfElse)
Compile conditional expressions using jumping instructions in the VM.
┌────────────────────┐
│ 0 [ conditional ] │
│ 1 JUMP_IF_FALSE →┼─┐
│ 2 [ main body ] │ │ Jump to else body if
┌┼─3─← JUMP │ │ condition is false.
Jump over else body ││ 4 [ else body ]←┼─┘
if condition is true.└┼─5─→ ... │
└────────────────────┘
sourcefn compile_with(&mut self, slot: LocalIdx, node: &With)
fn compile_with(&mut self, slot: LocalIdx, node: &With)
Compile with
expressions by emitting instructions that
pop/remove the indices of attribute sets that are implicitly
in scope through with
on the “with-stack”.
sourcefn compile_param_pattern(&mut self, pattern: &Pattern) -> (Formals, CodeIdx)
fn compile_param_pattern(&mut self, pattern: &Pattern) -> (Formals, CodeIdx)
Compiles pattern function arguments, such as { a, b }: ...
.
These patterns are treated as a special case of locals binding
where the attribute set itself is placed on the first stack
slot of the call frame (either as a phantom, or named in case
of an @
binding), and the function call sets up the rest of
the stack as if the parameters were rewritten into a let
binding.
For example:
({ a, b ? 2, c ? a * b, ... }@args: <body>) { a = 10; }
would be compiled similarly to a binding such as
let args = { a = 10; };
in let a = args.a;
b = args.a or 2;
c = args.c or a * b;
in <body>
However, there are two properties of pattern function arguments that can not be compiled by desugaring in this way:
- Bindings have to fail if too many arguments are provided. This is done by emitting a special instruction that checks the set of keys from a constant containing the expected keys.
- Formal arguments with a default expression are (as an optimization and because it is simpler) not wrapped in another thunk, instead compiled and accessed separately. This means that the default expression may never make it into the local’s stack slot if the argument is provided by the caller. We need to take this into account and skip any operations specific to the expression like thunk finalisation in such cases.
fn compile_lambda(&mut self, slot: LocalIdx, node: &Lambda) -> Option<CodeIdx>
fn thunk<N, F>(&mut self, outer_slot: LocalIdx, node: &N, content: F)
sourcefn compile_lambda_or_thunk<N, F>(
&mut self,
is_suspended_thunk: bool,
outer_slot: LocalIdx,
node: &N,
content: F,
)
fn compile_lambda_or_thunk<N, F>( &mut self, is_suspended_thunk: bool, outer_slot: LocalIdx, node: &N, content: F, )
Compile an expression into a runtime closure or thunk
fn compile_apply(&mut self, slot: LocalIdx, node: &Apply)
sourcefn emit_upvalue_data<T: ToSpan>(
&mut self,
slot: LocalIdx,
_: &T,
upvalues: Vec<Upvalue>,
capture_with: bool,
)
fn emit_upvalue_data<T: ToSpan>( &mut self, slot: LocalIdx, _: &T, upvalues: Vec<Upvalue>, capture_with: bool, )
Emit the data instructions that the runtime needs to correctly assemble the upvalues struct.
sourcefn emit_literal_ident(&mut self, ident: &Ident)
fn emit_literal_ident(&mut self, ident: &Ident)
Emit the literal string value of an identifier. Required for several operations related to attribute sets, where identifiers are used as string keys.
sourcefn patch_jump(&mut self, idx: CodeIdx)
fn patch_jump(&mut self, idx: CodeIdx)
Patch the jump instruction at the given index, setting its jump offset from the placeholder to the current code position.
This is required because the actual target offset of jumps is not known at the time when the jump operation itself is emitted.
sourcefn cleanup_scope<N: ToSpan>(&mut self, node: &N)
fn cleanup_scope<N: ToSpan>(&mut self, node: &N)
Decrease scope depth of the current function and emit instructions to clean up the stack at runtime.
sourcefn new_context(&mut self)
fn new_context(&mut self)
Open a new lambda context within which to compile a function, closure or thunk.
sourcefn declare_local<S: Into<String>, N: ToSpan>(
&mut self,
node: &N,
name: S,
) -> LocalIdx
fn declare_local<S: Into<String>, N: ToSpan>( &mut self, node: &N, name: S, ) -> LocalIdx
Declare a local variable known in the scope that is being compiled by pushing it to the locals. This is used to determine the stack offset of variables.
sourcefn has_dynamic_ancestor(&mut self) -> bool
fn has_dynamic_ancestor(&mut self) -> bool
Determine whether the current lambda context has any ancestors
that use dynamic scope resolution, and mark contexts as
needing to capture their enclosing with
-stack in their
upvalues.
fn emit_force<N: ToSpan>(&mut self, node: &N)
fn emit_warning<N: ToSpan>(&mut self, node: &N, kind: WarningKind)
fn emit_error<N: ToSpan>(&mut self, node: &N, kind: ErrorKind)
Auto Trait Implementations§
impl<'source, 'observer> Freeze for Compiler<'source, 'observer>
impl<'source, 'observer> !RefUnwindSafe for Compiler<'source, 'observer>
impl<'source, 'observer> !Send for Compiler<'source, 'observer>
impl<'source, 'observer> !Sync for Compiler<'source, 'observer>
impl<'source, 'observer> Unpin for Compiler<'source, 'observer>
impl<'source, 'observer> !UnwindSafe for Compiler<'source, 'observer>
Blanket Implementations§
source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
source§impl<T> IntoEither for T
impl<T> IntoEither for T
source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self
into a Left
variant of Either<Self, Self>
if into_left
is true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read moresource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self
into a Left
variant of Either<Self, Self>
if into_left(&self)
returns true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read more