Embedding Instructions
This section is tailored for developers who intendextend the functionality of the UniotLisp interpreter and understand the details better. It covers setup, initialization, extending the interpreter with custom primitives or constants, utilizing the API, and handling advanced configurations.
Getting Started
To begin using UniotLisp, follow these steps:
Obtain the Source Code:
Download or clone the uniot-lisp repository from GitHub:
Ensure you have access to the
libminilisp.h
,libminilisp.c
andmemcheck.h
files.Include in Your Project:
Add
libminilisp.c
to your project's source files.Include the header in your code:
Define Print Functions:
Initialize an Environment:
Initialize the Interpreter and Create an Environment:
Evaluate Lisp Code:
Cleanup:
Full Example Code:
This code evaluates the expression
(+ 1 2 3)
, which results in6
.
Basic Concepts
Understanding the foundational concepts of UniotLisp will aid in effectively utilizing and extending the interpreter.
Object Representation
UniotLisp represents all Lisp entities as Obj
structures. Each Obj
can represent various data types, such as integers, symbols, lists (cons cells), functions, macros, and more.
Obj Structure
Type Tags
TINT
: IntegerTCELL
: Cons cell (pair)TSYMBOL
: SymbolTPRIMITIVE
: Primitive functionTFUNCTION
: User-defined functionTMACRO
: MacroTENV
: Environment frameTMOVED
: Forwarding pointer (used by GC)TTRUE
,TNIL
,TDOT
,TCPAREN
: Special constants
Memory Management and Garbage Collection
UniotLisp employs a copying garbage collector based on Cheney's algorithm to manage memory efficiently.
Copying Garbage Collector
Semispace Allocation: The heap is divided into two equal halves (
memory
andfrom_space
). Objects are copied from the active semispace to the other during garbage collection.Forwarding Pointers: When an object is moved, a forwarding pointer (
TMOVED
type) is placed in its original location to avoid duplicate copies.Root Management: Roots are managed through macros (
ADD_ROOT
,DEFINE1
, etc.) that track pointers on the C stack to ensure live objects are retained.
Allocation Strategy
Memory Allocation (
alloc
): Allocates memory for new objects, triggering garbage collection if necessary.Memory Exhaustion: If memory cannot be allocated even after GC, the interpreter terminates with an error.
Parsing and Evaluation
UniotLisp parses Lisp code using a recursive-descent parser and evaluates expressions in an environment model.
Parsing
Tokenization: The
read_expr
function reads characters, identifies tokens (integers, symbols, parentheses), and constructs correspondingObj
structures.Symbol Interning: Symbols are interned to ensure uniqueness, optimizing symbol comparisons and storage.
Evaluation
Evaluator (
eval
): ProcessesObj
structures, handling self-evaluating objects, variable lookups, function applications, and macro expansions.Environment Model: Variables are stored in environments (
TENV
), allowing for lexical scoping and nested environments.
Adding New Primitives
Extending UniotLisp with new primitives allows you to introduce custom functionalities tailored to your application's needs. This section guides you through the process of defining and registering new primitive functions.
Defining a Primitive Function
A primitive function in UniotLisp is a C function that follows a specific signature:
Parameters:
void *root
: Root pointer for garbage collection.struct Obj **env
: Current environment.struct Obj **args
: List of arguments passed to the primitive.
Return Value: A pointer to an Obj
representing the result.
Example: Define a primitive that adds two integers.
Registering the Primitive
After defining the primitive function, you need to register it within the interpreter's environment using add_primitive
.
Function Signature:
Parameters:
void *root
: Root pointer for garbage collection.Obj **env
: Current environment.const char *name
: Name of the primitive as it will appear in Lisp.Primitive *fn
: Pointer to the C function implementing the primitive.
Example: Register the add_two
primitive.
Usage in Lisp Code:
Adding Constants
Defining constants allows you to introduce fixed values that can be referenced within Lisp code. Constants are immutable and cannot be reassigned.
Use add_constant
or add_constant_int
to register the constant within the environment.
Function Signatures:
Registering the Constant
Example: Register the #version
constant.
or
Usage in Lisp Code:
Error Handling
UniotLisp incorporates robust error handling to ensure the interpreter remains stable and provides informative feedback.
Error Function
The error
function is used to report errors and terminate the current evaluation gracefully.
Usage:
Non-Local Exits
UniotLisp uses setjmp
and longjmp
to handle errors without crashing the interpreter. When an error occurs, the interpreter jumps back to a safe state, allowing for continued operation or graceful termination.
Common Error Scenarios
Some common error scenarios encountered during Lisp evaluation include:
Malformed Expressions: Incorrect syntax or structure.
Type Mismatches: Operations on incompatible types.
Undefined Symbols: Referencing symbols that haven't been defined.
Memory Exhaustion: Running out of allocated memory.
API Usage
UniotLisp provides a set of API functions to interact with the interpreter programmatically. These functions facilitate creating environments, evaluating code, managing memory, and customizing interpreter behavior.
Lifecycle Management
lisp_create
: Initializes the interpreter with a specified memory size.lisp_destroy
: Cleans up and frees allocated memory.lisp_is_created
: Checks if the interpreter has been initialized.
Evaluation Interface
lisp_eval
: Parses and evaluates Lisp code from a string.safe_eval
: Evaluates an expression with error protection.
Configuration
lisp_set_cycle_yield
: Sets a yield function to allow cooperative multitasking during long-running operations.Define a yield function that conforms to the
yield_def
type:Register the yield function:
lisp_set_printers
: Configures output handlers for standard output, logs, and errors.
System Information
lisp_mem_used
: Retrieves the amount of memory used.lisp_error_idx
: Gets the current index in the input buffer where an error occurred.
Conclusion
UniotLisp offers a lightweight yet powerful Lisp interpreter suitable for embedding within C applications. Its minimalist design ensures ease of integration, while its extensible architecture allows developers to tailor its capabilities to specific requirements. By understanding its core concepts, utilizing built-in primitives, and leveraging the ability to add custom functionalities, developers can effectively harness UniotLisp for a variety of applications.
For further assistance or to contribute to the project, please refer to the project's repository or contact the maintainers.
Last updated