x-engine-c v0.2.13
The C engine for x-lang
Loading...
Searching...
No Matches
operative.h File Reference

Operative (fexpr / dynamic-scope combiner) type for x-lang. More...

#include "x-type.h"
#include "x-type/prim.h"

Go to the source code of this file.

Macros

#define X_TYPE_OPERATIVE_NAME   "OPERATIVE"
 
Type predicates
#define x_obj_type_isoperative(B, X)   x_obj_is_type((B), (X), X_TYPE_OPERATIVE_NAME)
 
State accessors

Operative state list: (params . (envparam . (body . env))). Stored in x_callable_state (slot 1) of [fn-ptr][state] layout. GC traverses via the p_units=2 fallback in x_type_heap_mark.

#define x_opstate(X)   x_callable_state((X))
 
#define x_opparams(X)   x_firstobj(x_opstate((X)))
 
#define x_openvparam(X)   x_firstobj(x_restobj(x_opstate((X))))
 
#define x_opbody(X)   x_firstobj(x_restobj(x_restobj(x_opstate((X)))))
 
#define x_openv(X)   x_restobj(x_restobj(x_restobj(x_opstate((X)))))
 
Convenience constructors
#define x_mkop(B, P, EP, BD, E)   x_make_operative((B), X_OBJ_FLAG_NONE, (P), (EP), (BD), (E))
 

Functions

x_obj_tx_make_operative (x_obj_t *p_base, x_obj_flag_t flags, x_obj_t *p_params, x_obj_t *p_envparam, x_obj_t *p_body, x_obj_t *p_env)
 
x_obj_tx_type_operative_register (x_obj_t *p_base, x_obj_t *p_args)
 
x_obj_tx_type_operative_struct (x_obj_t *p_base, x_obj_t *p_args)
 
x_obj_tx_type_operative_make (x_obj_t *p_base, x_obj_t *p_args)
 
x_obj_tx_type_operative_call (x_obj_t *p_base, x_obj_t *p_args)
 

Variables

Static primitive atoms for the type struct.
x_satom_t x_type_operative_name
 
x_satom_t x_type_operative_make_prim
 
x_satom_t x_type_operative_call_prim
 
x_satom_t x_type_operative_struct_prim
 

Detailed Description

Operative (fexpr / dynamic-scope combiner) type for x-lang.

Author
Jon Ruttan (jonru.nosp@m.ttan.nosp@m.@gmai.nosp@m.l.co.nosp@m.m)

An operative is a two-unit heap object with callable layout:

slot 0 slot 1 (state_list)
+-----------------+ +------------------------------------------+
| fn_ptr | | (params . (envparam . (body . env))) |
| (x_type_ | | |
| operative_call)| | params ---- formal parameter tree |
+-----------------+ | envparam -- symbol bound to caller env |
| body ------ list of body expressions |
| env ------- captured lexical env alist |
+------------------------------------------+
Note
Unlike procedures, the operative state list has NO BST field. Operatives receive unevaluated arguments and the caller's dynamic environment, so they do not need a captured BST snapshot.
Slot 0 holds a raw C function pointer, NOT a heap object. The GC mark callback (x_type_operative_mark) must skip slot 0 and only traverse slot 1 (the state list).
See also
x_type_operative_mark in operative.c
x_type_operative_call for the TCO call path

Macro Definition Documentation

◆ x_mkop

#define x_mkop (   B,
  P,
  EP,
  BD,
 
)    x_make_operative((B), X_OBJ_FLAG_NONE, (P), (EP), (BD), (E))

Make operative with default flags.

◆ x_obj_type_isoperative

#define x_obj_type_isoperative (   B,
 
)    x_obj_is_type((B), (X), X_TYPE_OPERATIVE_NAME)

Test if object is an operative.

◆ x_opbody

#define x_opbody (   X)    x_firstobj(x_restobj(x_restobj(x_opstate((X)))))

Body expression list.

◆ x_openv

#define x_openv (   X)    x_restobj(x_restobj(x_restobj(x_opstate((X)))))

Captured environment.

◆ x_openvparam

#define x_openvparam (   X)    x_firstobj(x_restobj(x_opstate((X))))

Environment parameter name.

◆ x_opparams

#define x_opparams (   X)    x_firstobj(x_opstate((X)))

Parameter tree.

◆ x_opstate

#define x_opstate (   X)    x_callable_state((X))

Full state list.

◆ X_TYPE_OPERATIVE_NAME

#define X_TYPE_OPERATIVE_NAME   "OPERATIVE"

Type name string.

Function Documentation

◆ x_make_operative()

x_obj_t * x_make_operative ( x_obj_t p_base,
x_obj_flag_t  flags,
x_obj_t p_params,
x_obj_t p_envparam,
x_obj_t p_body,
x_obj_t p_env 
)

Allocate a new operative object on the heap.

Allocate a new operative on the heap.

Builds the state list (params . (envparam . (body . env))) and stores it in slot 1 of the two-unit callable layout.

Parameters
p_basex_obj_t* – Base (execution context)
flagsx_obj_flag_t – Object flags
p_paramsx_obj_t* – Formal parameter tree
p_envparamx_obj_t* – Environment parameter name (or nil)
p_bodyx_obj_t* – Body expression list
p_envx_obj_t* – Captured environment
Returns
Heap-allocated operative object
Note
Constructor direction is DELIBERATELY the reverse of the simple atom/int/ptr types (#248): there x_make_X packs its one payload into an arg list and delegates to the x_type_X_make handler. An operative carries four fields, and this C-ABI constructor is the hot path (x_mkop, called on every op form via closure.c), so it builds the state tree directly; x_type_operative_make – the rare generic (make-instance) handler – adapts an arg list to it. Flipping to the simple-type direction would round-trip every op construction through a pack-then-unpack for no gain.

◆ x_type_operative_call()

x_obj_t * x_type_operative_call ( x_obj_t p_base,
x_obj_t p_args 
)

Type-dispatch call callback – evaluate an operative application.

Type-dispatch call callback: evaluate an operative application.

Operatives are lexically scoped: the body runs in the env captured at (op ...) creation time, extended with the formals bound to the UNEVALUATED arguments. The caller's environment is exposed only through the named env-param (when the op declares one), so that (eval expr e) or (tail-eval expr e) inside the body can reach back into the caller's scope explicitly. Caller-local names cannot silently shadow the globals the body relies on. Operatives do NOT get self-passing.

Implementation:

  • The body's environment is a child of the captured environment with the formals bound in it, and the env-param bound to the caller's environment as a value. The caller's locals are not on this chain, so they are invisible to the body by construction.

Body is run via x_eval_body (NOT a TCO body evaluator). Each body form is evaluated synchronously to completion – tail-eval inside a form still TCOs via x_eval's own trampoline, so common patterns like (tail-eval (...) e) work without growing the C stack. The op itself doesn't TCO into its caller, but lib ops typically dispatch the heavy work via tail-eval into the caller's env, so the op frame isn't on the recursion path.

After the body returns, the caller's environment is made current again, unconditionally. A (def ...) the body evaluated in the caller's environment is IN that environment, so restoring the pointer keeps it.

Parameters
p_basex_obj_t* – Base (execution context)
p_argsx_obj_t* – (operative . unevaluated-args)
Returns
Result of the operative body

◆ x_type_operative_make()

x_obj_t * x_type_operative_make ( x_obj_t p_base,
x_obj_t p_args 
)

Type-dispatch make callback for OPERATIVE.

Type-dispatch make callback: construct an operative from x-lang args.

Expects args: (params envparam body env [flags]).

Parameters
p_basex_obj_t* – Base (execution context)
p_argsx_obj_t* – Construction arguments
Returns
New operative object

◆ x_type_operative_register()

x_obj_t * x_type_operative_register ( x_obj_t p_base,
x_obj_t p_args 
)

Register (or retrieve) the OPERATIVE type struct on p_base.

Register (or retrieve) the OPERATIVE type struct on p_base.

Parameters
p_basex_obj_t* – Base (execution context)
p_argsx_obj_t* – Unused
Returns
The registered type struct object

◆ x_type_operative_struct()

x_obj_t * x_type_operative_struct ( x_obj_t p_base,
x_obj_t p_args 
)

Build the OPERATIVE type struct descriptor.

Variable Documentation

◆ x_type_operative_call_prim

x_satom_t x_type_operative_call_prim

◆ x_type_operative_make_prim

x_satom_t x_type_operative_make_prim

◆ x_type_operative_name

x_satom_t x_type_operative_name
extern

◆ x_type_operative_struct_prim

x_satom_t x_type_operative_struct_prim