Contents Build Your Own Lisp
- About
- Who this is for
- Why learn C
- How to learn C
- Why build a Lisp
- Your own Lisp
- Setup
- Text Editor
- Compiler
- Hello World
- Compilation
- Errors
- Documentation
- Overview
- Programs
- Variables
- Function Declarations
- Structure Declarations
- Pointers
- Strings
- Conditionals
- Loops
- Read, Evaluate, Print
- An Interactive Prompt
- Compilation
- Editing input
- The C Preprocessor
- What is a Programming Language?
- Parser Combinators
- Coding Grammars
- Natural Grammars
- Polish Notation
- Regular Expressions
- Installing mpc
- Polish Notation Grammar
- Parsing User Input
- Trees
- Recursion
- Evaluation
- Printing
- Crashes
- Lisp Value
- Enumerations
- Lisp Value Functions
- Evaluating Errors
- Plumbing
- Lists and Lisps
- Types of List
- Pointers
- The Stack & The Heap
- Parsing Expressions
- Expression Structure
- Constructors & Destructors
- Reading Expressions
- Printing Expressions
- Evaluating Expressions
- Adding Features
- Quoted Expressions
- Reading Q-Expressions
- Builtin Functions
- First Attempt
- Macros
- Builtins Lookup
- Immutability
- Function Pointers
- Cyclic Types
- Function Type
- Environment
- Variable Evaluation
- Builtins
- Define Function
- Error Reporting
- What is a Function?
- Function Type
- Lambda Function
- Parent Environment
- Function Calling
- Variable Arguments
- Interesting Functions
- Doing it yourself
- Ordering
- Equality
- If Function
- Recursive Functions
- Libraries
- String Type
- Reading Strings
- Comments
- Load Function
- Command Line Arguments
- Print Function
- Error Function
- Finishing Up
- Minimalism
- Atom
- Building Blocks
- Logical Operators
- Miscellaneous Functions
- List Functions
- Conditional Functions
- Fibonacci
- Only the Beginning
- Native Types
- User Defined Types
- List Literal
- Operating System Interaction
- Macros
- Variable Hashtable
- Pool Allocation
- Garbage Collection
- Tail Call Optimisation
- Lexical Scoping
- Static Typing
- Conclusion
Github
Q-Expressions Chapter 10
Adding Features
You'll notice that the following chapters will all follow a similar pattern. This pattern is the typical approach used to add new features to a language. It consists of a number of steps that bring a feature from start to finish. These are listed below, and are exactly what we're going to do in this chapter to introduce a new feature called a Q-Expression.
Syntax | Add new rule to the language grammar for this feature. |
Representation | Add new data type variation to represent this feature. |
Parsing | Add new functions for reading this feature from the abstract syntax tree. |
Semantics | Add new functions for evaluating and manipulating this feature. |
Quoted Expressions
In this chapter we'll implement a new type of Lisp Value called a Q-Expression.
This stands for quoted expression, and is a type of Lisp Expression that is not evaluated by the standard Lisp mechanics. When encountered by the evaluation function Q-expressions are left exactly as they are. This makes them ideal for a number of purposes. We can use them to store and manipulate other Lisp values such as numbers, symbols, or other S-Expressions themselves.
After we've added Q-Expressions we are going to implement a concise set of operators to manipulate them. Like the arithmetic operators these will prove fundamental in how we think about and play with expressions.
The syntax for Q-Expressions is very similar to that of S-Expressions. The only difference is that instead of parenthesis ()
Q-Expressions are surrounded by curly brackets {}
. We can add this to our grammar as follows.
I've never heard of Q-Expressions.
Q-Expressions don't exist in other Lisps. Other Lisps use Macros to stop evaluation. These look like normal functions, but they do not evaluate their arguments. A special Macro called quote '
exists, which can be used to stop the evaluation of almost anything. This is the inspiration for Q-Expressions, which are unique to our Lisp, and will be used instead of Macros for doing all the same tasks and more.
The way I've used S-Expression and Q-Expression in this book is a slight abuse of terminology, but I hope this misdemeanor makes the behaviour of our Lisp clearer.
mpc_parser_t* Number = mpc_new("number");mpc_parser_t* Symbol = mpc_new("symbol");mpc_parser_t* Sexpr = mpc_new("sexpr");mpc_parser_t* Qexpr = mpc_new("qexpr");mpc_parser_t* Expr = mpc_new("expr");mpc_parser_t* Lispy = mpc_new("lispy");mpca_lang(MPCA_LANG_DEFAULT, " \ number : /-?[0-9]+/ ; \ symbol : '+' | '-' | '*' | '/' ; \ sexpr : '(' * ')' ; \ qexpr : '{' * '}' ; \ expr : | | | ; \ lispy : /^/ * /$/ ; \ ", Number, Symbol, Sexpr, Qexpr, Expr, Lispy);
We also must remember to update our cleanup function to deal with the new rule we've added.
mpc_cleanup(6, Number, Symbol, Sexpr, Qexpr, Expr, Lispy);
Reading Q-Expressions
Because Q-Expressions are so similar S-Expressions much of their internal behaviour is going to be the same. We're going to reuse our S-Expression data fields to represent Q-Expressions, but we still need to add a separate type to the enumeration.
enum { LVAL_ERR, LVAL_NUM, LVAL_SYM, LVAL_SEXPR, LVAL_QEXPR };
We can also add a constructor for this variation.
/* A pointer to a new empty Qexpr lval */lval* lval_qexpr(void) { lval* v = malloc(sizeof(lval)); v->type = LVAL_QEXPR; v->count = 0; v->cell = NULL; return v;}
To print and delete Q-Expressions we do essentially the same thing as with S-Expressions. We can add the relevant lines to our functions for printing and deletion as follows.
void lval_print(lval* v) { switch (v->type) { case LVAL_NUM: printf("%li", v->num); break; case LVAL_ERR: printf("Error: %s", v->err); break; case LVAL_SYM: printf("%s", v->sym); break; case LVAL_SEXPR: lval_expr_print(v, '(', ')'); break; case LVAL_QEXPR: lval_expr_print(v, '{', '}'); break; }}
void lval_del(lval* v) { switch (v->type) { case LVAL_NUM: break; case LVAL_ERR: free(v->err); break; case LVAL_SYM: free(v->sym); break; /* If Qexpr or Sexpr then delete all elements inside */ case LVAL_QEXPR: case LVAL_SEXPR: for (int i = 0; i < v->count; i++) { lval_del(v->cell[i]); } /* Also free the memory allocated to contain the pointers */ free(v->cell); break; } free(v);}