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<'_, '_>

AST-traversing functions related to bindings.

source

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.

source

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.

source

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.

source

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:

  1. Keys can be dynamically constructed through interpolation.
  2. Keys can refer to nested attribute sets.
  3. Attribute sets can (optionally) be recursive.
source

pub(super) fn compile_env(&mut self, env: &HashMap<SmolStr, Value>)

Emit definitions for all variables in the top-level global env passed to the evaluation (eg local variables in the REPL)

source

fn bind_values(&mut self, bindings: TrackedBindings)

Actually binds all tracked bindings by emitting the bytecode that places them in their stack slots.

source

fn compile_bindings<N>(&mut self, slot: LocalIdx, kind: BindingsKind, node: &N)
where N: ToSpan + HasEntryProxy,

source

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.

source

pub(super) fn compile_legacy_let(&mut self, slot: LocalIdx, node: &LegacyLet)

source

pub(super) fn is_user_defined(&mut self, ident: &str) -> bool

Is the given identifier defined by the user in any current scope?

source

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.

source

pub(super) fn compile_ident(&mut self, slot: LocalIdx, node: &Ident)

source§

impl Compiler<'_, '_>

Private compiler helpers related to bindings.

source

fn resolve_upvalue<N: ToSpan>( &mut self, ctx_idx: usize, name: &str, node: &N ) -> Option<UpvalueIdx>

source

fn add_upvalue<N: ToSpan>( &mut self, ctx_idx: usize, node: &N, kind: UpvalueKind ) -> UpvalueIdx

source§

impl Compiler<'_, '_>

source

pub(crate) fn span_for<S: ToSpan>(&self, to_span: &S) -> Span

source§

impl<'source, 'observer> Compiler<'source, 'observer>

Compiler construction

source

pub(crate) fn new( location: Option<PathBuf>, globals: Rc<GlobalsMap>, env: Option<&HashMap<SmolStr, Value>>, source: &'source SourceCode, file: &'source File, observer: &'observer mut dyn CompilerObserver ) -> EvalResult<Self>

source§

impl Compiler<'_, '_>

source

fn context(&self) -> &LambdaCtx

source

fn context_mut(&mut self) -> &mut LambdaCtx

source

fn chunk(&mut self) -> &mut Chunk

source

fn scope(&self) -> &Scope

source

fn scope_mut(&mut self) -> &mut Scope

source

fn push_op<T: ToSpan>(&mut self, data: OpCode, node: &T) -> CodeIdx

Push a single instruction to the current bytecode chunk and track the source span from which it was compiled.

source

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<'_, '_>

source

fn compile(&mut self, slot: LocalIdx, expr: Expr)

source

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.

source

fn compile_literal(&mut self, node: &Literal)

source

fn compile_path(&mut self, slot: LocalIdx, node: &Path)

source

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.

source

fn compile_str(&mut self, slot: LocalIdx, node: &Str)

source

fn compile_unary_op(&mut self, slot: LocalIdx, op: &UnaryOp)

source

fn compile_binop(&mut self, slot: LocalIdx, op: &BinOp)

source

fn compile_and(&mut self, slot: LocalIdx, node: &BinOp)

source

fn compile_or(&mut self, slot: LocalIdx, node: &BinOp)

source

fn compile_implication(&mut self, slot: LocalIdx, node: &BinOp)

source

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.

source

fn compile_attr(&mut self, slot: LocalIdx, node: &Attr)

source

fn compile_has_attr(&mut self, slot: LocalIdx, node: &HasAttr)

source

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.

source

fn compile_select(&mut self, slot: LocalIdx, node: &Select)

source

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 ...                     │   │ ..   ....               │
 └────────────────────────────┘   └─────────────────────────┘
source

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─→     ...         │
                       └─────────────────────┘
source

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─→     ...        │
                       └────────────────────┘
source

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”.

source

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:

  1. 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.
  2. 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.
source

fn compile_lambda(&mut self, slot: LocalIdx, node: &Lambda) -> Option<CodeIdx>

source

fn thunk<N, F>(&mut self, outer_slot: LocalIdx, node: &N, content: F)
where N: ToSpan, F: FnOnce(&mut Compiler<'_, '_>, LocalIdx),

source

fn unthunk(&mut self)

Mark the current thunk as redundant, i.e. possible to merge directly into its parent lambda context without affecting runtime behaviour.

source

fn compile_lambda_or_thunk<N, F>( &mut self, is_suspended_thunk: bool, outer_slot: LocalIdx, node: &N, content: F )
where N: ToSpan, F: FnOnce(&mut Compiler<'_, '_>, LocalIdx) -> Option<CodeIdx>,

Compile an expression into a runtime closure or thunk

source

fn compile_apply(&mut self, slot: LocalIdx, node: &Apply)

source

fn emit_upvalue_data<T: ToSpan>( &mut self, slot: LocalIdx, node: &T, upvalues: Vec<Upvalue>, capture_with: bool )

Emit the data instructions that the runtime needs to correctly assemble the upvalues struct.

source

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.

source

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.

source

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.

source

fn new_context(&mut self)

Open a new lambda context within which to compile a function, closure or thunk.

source

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.

source

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.

source

fn emit_force<N: ToSpan>(&mut self, node: &N)

source

fn emit_warning<N: ToSpan>(&mut self, node: &N, kind: WarningKind)

source

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> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> Same for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

source§

fn vzip(self) -> V