Compare commits

...

32 Commits

Author SHA1 Message Date
c26c723eea Fetch proc before evaluating arguments 2025-08-10 21:37:13 +01:00
068692e163 Push proc only once in procedure application 2025-08-10 21:36:00 +01:00
167e91d816 Add call AM operation 2025-08-10 21:33:25 +01:00
426d270318 Create car, cdr and assign AM operations 2025-08-10 21:15:36 +01:00
eaef6f3cd3 Add register parameters to AM append operation 2025-08-10 20:51:08 +01:00
76efcef953 Add register parameter to AM stack operations 2025-08-10 20:51:06 +01:00
57c39fd00b Make AM registers into an array 2025-08-10 20:31:54 +01:00
52a42d2937 Add integration tests for nested arithmetic 2025-08-10 19:55:19 +01:00
ba0a543a4c Handle negative numbers in print 2025-08-10 19:54:58 +01:00
662b99a40f Handle argument evaluation 2025-08-10 19:54:21 +01:00
624311d04f Add am_append_arg procedure 2025-08-10 19:44:05 +01:00
6077cf571d Create demo application 2025-08-10 17:27:23 +01:00
9df53c63c5 Create REPL module 2025-08-10 17:26:46 +01:00
5c585ad35c Add integration tests 2025-08-10 17:26:32 +01:00
e25975e29d Create read module 2025-08-10 17:26:32 +01:00
d90f91cda0 Create print module 2025-08-10 17:26:32 +01:00
d5173243ba Implement primitive procedure application evaluation 2025-08-10 16:34:11 +01:00
d8e51b0aa0 Add store into abstract machine struct 2025-08-10 15:24:18 +01:00
b20a6749f7 Implement +, * and - primitives 2025-08-10 15:08:34 +01:00
472a682757 Add primitive procedures to expression type 2025-08-10 14:41:37 +01:00
3f871d2b43 Implement atom evaluation 2025-08-10 13:58:36 +01:00
03bd0ff597 Consolidate expression creation functions in expr module 2025-08-10 13:48:59 +01:00
fade9395fa Create environment module 2025-08-10 02:05:16 +01:00
a03ef58eca Implement parsing 2025-08-10 02:03:42 +01:00
f97cea9290 Add minimal abstract machine module 2025-08-10 02:03:42 +01:00
e50fd10f9b Disable memset/memcpy messages from Clang-Tidy 2025-08-09 19:35:05 +01:00
f41d3a949c Create simple, bump allocator store 2025-08-09 19:35:05 +01:00
23bbbea755 Define expression data type 2025-08-09 19:34:50 +01:00
00d1961d36 Add scripts for checking formatting, linting etc 2025-08-09 19:34:50 +01:00
3e8a9d6789 Implement tokenisation 2025-08-09 15:33:52 +01:00
657f0922bb Add license (Komorebi) 2025-08-09 10:31:26 +01:00
c8bacab6fe Add build/ directory to .gitignore 2025-08-09 10:28:47 +01:00
48 changed files with 2525 additions and 0 deletions

1
.clang-tidy Normal file
View File

@@ -0,0 +1 @@
Checks: '-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling'

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build/

30
CMakeLists.txt Normal file
View File

@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.15)
project(imp LANGUAGES C)
option(TESTS "Build tests" ON)
option(DEMO "Build demo" ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
function(configure_target target)
# Set C standard and compile flags
set_target_properties(${target} PROPERTIES
C_STANDARD 11
C_STANDARD_REQUIRED ON
C_EXTENSIONS OFF
)
target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic)
endfunction()
add_subdirectory(lib)
if (${TESTS})
enable_testing()
add_subdirectory(dep/unity)
add_subdirectory(tests)
endif()
if (${DEMO})
add_subdirectory(demo)
endif()

108
LICENSE.md Normal file
View File

@@ -0,0 +1,108 @@
# Komorebi License
Version 2.0.0
## Acceptance
In order to get any license under these terms, you must agree
to them as both strict obligations and conditions to all
your licenses.
## Copyright License
The licensor grants you a copyright license for the software
to do everything you might do with the software that would
otherwise infringe the licensor's copyright in it for any
permitted purpose. However, you may only distribute the source
code of the software according to the [Distribution License](
#distribution-license), you may only make changes according
to the [Changes License](#changes-license), and you may not
otherwise distribute the software or new works based on the
software.
## Distribution License
The licensor grants you an additional copyright license to
distribute copies of the source code of the software. Your
license to distribute covers distributing the source code of
the software with changes permitted by the [Changes License](
#changes-license).
## Changes License
The licensor grants you an additional copyright license to
make changes for any permitted purpose.
## Patent License
The licensor grants you a patent license for the software that
covers patent claims the licensor can license, or becomes able
to license, that you would infringe by using the software.
## Personal Uses
Personal use for research, experiment, and testing for
the benefit of public knowledge, personal study, private
entertainment, hobby projects, amateur pursuits, or religious
observance, without any anticipated commercial application,
is use for a permitted purpose.
## Fair Use
You may have "fair use" rights for the software under the
law. These terms do not limit them.
## No Other Rights
These terms do not allow you to sublicense or transfer any of
your licenses to anyone else, or prevent the licensor from
granting licenses to anyone else. These terms do not imply
any other licenses.
## Patent Defense
If you make any written claim that the software infringes or
contributes to infringement of any patent, your patent license
for the software granted under these terms ends immediately. If
your company makes such a claim, your patent license ends
immediately for work on behalf of your company.
## Violations
The first time you are notified in writing that you have
violated any of these terms, or done anything with the software
not covered by your licenses, your licenses can nonetheless
continue if you come into full compliance with these terms,
and take practical steps to correct past violations, within
32 days of receiving notice. Otherwise, all your licenses
end immediately.
## No Liability
***As far as the law allows, the software comes as is, without
any warranty or condition, and the licensor will not be liable
to you for any damages arising out of these terms or the use
or nature of the software, under any kind of legal claim.***
## Definitions
The **licensor** is the individual or entity offering these
terms, and the **software** is the software the licensor makes
available under these terms.
**You** refers to the individual or entity agreeing to these
terms.
**Your company** is any legal entity, sole proprietorship,
or other kind of organization that you work for, plus all
organizations that have control over, are under the control of,
or are under common control with that organization. **Control**
means ownership of substantially all the assets of an entity,
or the power to direct its management and policies by vote,
contract, or otherwise. Control can be direct or indirect.
**Your licenses** are all the licenses granted to you for the
software under these terms.
**Use** means anything you do with the software requiring one
of your licenses.

5
demo/CMakeLists.txt Normal file
View File

@@ -0,0 +1,5 @@
add_executable(repl
main.c
)
configure_target(repl)
target_link_libraries(repl PRIVATE imp)

9
demo/main.c Normal file
View File

@@ -0,0 +1,9 @@
#include "repl.h"
static repl_t repl;
int main(void)
{
repl_init(&repl);
return repl_run(&repl);
}

16
lib/CMakeLists.txt Normal file
View File

@@ -0,0 +1,16 @@
add_library(imp
am.c
env.c
eval.c
expr.c
memory_stream.c
parse.c
prim.c
print.c
read.c
repl.c
store.c
token.c
)
target_include_directories(imp PUBLIC include)
configure_target(imp)

63
lib/am.c Normal file
View File

@@ -0,0 +1,63 @@
#include "am.h"
#include "env.h"
#include "store.h"
#include <assert.h>
#include <string.h>
void am_init(am_t *am)
{
memset(am, 0, sizeof(am_t));
am->sp = am->stack + AM_STACK_SIZE - 1;
store_init(am);
env_init(am);
}
void am_push(am_t *am, am_reg_t reg)
{
assert(am->sp >= am->stack);
*am->sp-- = am->regs[reg];
}
void am_pop(am_t *am, am_reg_t reg)
{
assert(am->sp < am->stack + AM_STACK_SIZE - 1);
am->regs[reg] = *++am->sp;
}
void am_append(am_t *am, am_reg_t list_reg, am_reg_t item_reg)
{
expr_t *list = am->regs[list_reg];
while (!list->is_atom)
list = list->pair.cdr;
list->is_atom = false;
list->pair.car = am->regs[item_reg];
list->pair.cdr = expr_empty_list(am);
}
void am_car(am_t *am, am_reg_t dest, am_reg_t src)
{
assert(!am->regs[src]->is_atom);
am->regs[dest] = am->regs[src]->pair.car;
}
void am_cdr(am_t *am, am_reg_t dest, am_reg_t src)
{
assert(!am->regs[src]->is_atom);
am->regs[dest] = am->regs[src]->pair.cdr;
}
void am_assign(am_t *am, am_reg_t dest, am_reg_t src)
{
am->regs[dest] = am->regs[src];
}
void am_call(am_t *am, am_reg_t proc)
{
assert(am->regs[proc]->is_atom);
assert(am->regs[proc]->atom.type == ATOM_TYPE_PRIM_PROC);
assert(am->regs[proc]->atom.prim_proc != NULL);
am->regs[proc]->atom.prim_proc(am);
}

76
lib/env.c Normal file
View File

@@ -0,0 +1,76 @@
#include "env.h"
#include "prim.h"
#include <assert.h>
#include <string.h>
static bool symbol_eq(symbol_t *s, symbol_t *t)
{
return s->len == t->len && memcmp(s->buf, t->buf, s->len) == 0;
}
static expr_t **lookup(am_t *am, bool *found)
{
assert(AM_EXPR(am) != NULL);
assert(AM_EXPR(am)->is_atom);
assert(AM_EXPR(am)->atom.type == ATOM_TYPE_SYMBOL);
if (AM_ENV(am)->is_atom) {
assert(AM_ENV(am)->atom.type == ATOM_TYPE_EMPTY_LIST);
*found = false;
return &AM_ENV(am);
}
expr_t *prev;
for (expr_t *list = AM_ENV(am); !list->is_atom; list = list->pair.cdr) {
assert(list != NULL);
expr_t *entry = list->pair.car;
assert(!entry->is_atom);
expr_t *key = entry->pair.car;
assert(key != NULL);
assert(key->is_atom);
assert(key->atom.type == ATOM_TYPE_SYMBOL);
if (symbol_eq(&AM_EXPR(am)->atom.symbol, &key->atom.symbol)) {
*found = true;
return &entry->pair.cdr;
}
prev = list;
}
assert(!prev->is_atom);
assert(prev->pair.cdr->is_atom);
assert(prev->pair.cdr->atom.type == ATOM_TYPE_EMPTY_LIST);
*found = false;
return &prev->pair.cdr;
}
void env_init(am_t *am)
{
AM_ENV(am) = expr_empty_list(am);
prim_load(am);
}
void env_fetch(am_t *am)
{
bool found;
expr_t *val = *lookup(am, &found);
AM_VAL(am) = found ? val : NULL;
}
void env_set(am_t *am)
{
bool found;
expr_t **loc = lookup(am, &found);
if (found) {
*loc = AM_VAL(am);
} else {
(*loc)->is_atom = false;
(*loc)->pair.cdr = expr_empty_list(am);
(*loc)->pair.car = expr_pair(am, AM_EXPR(am), AM_VAL(am));
}
}

53
lib/eval.c Normal file
View File

@@ -0,0 +1,53 @@
#include "eval.h"
#include "env.h"
#include <assert.h>
static void eval_atom(am_t *am)
{
switch (AM_EXPR(am)->atom.type) {
case ATOM_TYPE_EMPTY_LIST:
case ATOM_TYPE_INTEGER:
case ATOM_TYPE_PRIM_PROC:
am_assign(am, VAL, EXPR);
break;
case ATOM_TYPE_SYMBOL:
env_fetch(am);
break;
}
}
static void eval_list(am_t *am)
{
AM_ARGL(am) = expr_empty_list(am);
am_cdr(am, UNEV, EXPR);
am_car(am, EXPR, EXPR);
env_fetch(am);
am_push(am, VAL);
while (!AM_UNEV(am)->is_atom) {
am_push(am, ARGL);
am_car(am, EXPR, UNEV);
am_cdr(am, UNEV, UNEV);
am_push(am, UNEV);
eval(am);
am_pop(am, UNEV);
am_pop(am, ARGL);
am_append(am, ARGL, VAL);
}
am_pop(am, VAL);
am_call(am, VAL);
}
void eval(am_t *am)
{
if (AM_EXPR(am)->is_atom)
eval_atom(am);
else
eval_list(am);
}

56
lib/expr.c Normal file
View File

@@ -0,0 +1,56 @@
#include "expr.h"
#include "am.h"
#include <string.h>
expr_t *expr_integer(am_t *am, int64_t value)
{
expr_t *expr = store_alloc(am);
expr->is_atom = true;
expr->atom.type = ATOM_TYPE_INTEGER;
expr->atom.integer = value;
return expr;
}
expr_t *expr_symbol(am_t *am, const symbol_t *symbol)
{
expr_t *expr = store_alloc(am);
expr->is_atom = true;
expr->atom.type = ATOM_TYPE_SYMBOL;
memcpy(&expr->atom.symbol, symbol, sizeof(symbol_t));
return expr;
}
expr_t *expr_str_symbol(am_t *am, const char *str)
{
symbol_t symbol = { .len = strlen(str) };
memcpy(symbol.buf, str, symbol.len);
return expr_symbol(am, &symbol);
}
expr_t *expr_empty_list(am_t *am)
{
expr_t *expr = store_alloc(am);
expr->is_atom = true;
expr->atom.type = ATOM_TYPE_EMPTY_LIST;
return expr;
}
expr_t *expr_pair(am_t *am, expr_t *car, expr_t *cdr)
{
expr_t *expr = store_alloc(am);
expr->is_atom = false;
expr->pair.car = car;
expr->pair.cdr = cdr;
return expr;
}
expr_t *expr_prim_proc(am_t *am, prim_proc_t prim_proc)
{
expr_t *expr = store_alloc(am);
expr->is_atom = true;
expr->atom.type = ATOM_TYPE_PRIM_PROC;
expr->atom.prim_proc = prim_proc;
return expr;
}

40
lib/include/am.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef AM_H
#define AM_H
#include "expr.h"
#include "store.h"
#define AM_STACK_SIZE 128U
#define AM_ARGL(am) am->regs[ARGL]
#define AM_ENV(am) am->regs[ENV]
#define AM_EXPR(am) am->regs[EXPR]
#define AM_UNEV(am) am->regs[UNEV]
#define AM_VAL(am) am->regs[VAL]
typedef enum {
ARGL,
ENV,
EXPR,
UNEV,
VAL,
AM_REG_COUNT,
} am_reg_t;
typedef struct am {
expr_t *regs[AM_REG_COUNT];
expr_t **sp, *stack[AM_STACK_SIZE];
store_t store;
} am_t;
void am_init(am_t *am);
void am_push(am_t *am, am_reg_t reg);
void am_pop(am_t *am, am_reg_t reg);
void am_append(am_t *am, am_reg_t list_reg, am_reg_t item_reg);
void am_car(am_t *am, am_reg_t dest, am_reg_t src);
void am_cdr(am_t *am, am_reg_t dest, am_reg_t src);
void am_assign(am_t *am, am_reg_t dest, am_reg_t src);
void am_call(am_t *am, am_reg_t proc);
#endif

10
lib/include/env.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef ENV_H
#define ENV_H
#include "am.h"
void env_init(am_t *am);
void env_fetch(am_t *am);
void env_set(am_t *am);
#endif

8
lib/include/eval.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef EVAL_H
#define EVAL_H
#include "am.h"
void eval(am_t *am);
#endif

56
lib/include/expr.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef EXPR_H
#define EXPR_H
#define MAX_SYMBOL_LEN 32U
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct am;
typedef struct {
char buf[MAX_SYMBOL_LEN];
size_t len;
} symbol_t;
typedef void (*prim_proc_t)(struct am *am);
typedef enum {
ATOM_TYPE_EMPTY_LIST,
ATOM_TYPE_INTEGER,
ATOM_TYPE_SYMBOL,
ATOM_TYPE_PRIM_PROC,
} atom_type_t;
typedef struct {
atom_type_t type;
union {
int64_t integer;
symbol_t symbol;
prim_proc_t prim_proc;
};
} atom_t;
struct expr;
typedef struct {
struct expr *car, *cdr;
} pair_t;
typedef struct expr {
bool is_atom;
union {
atom_t atom;
pair_t pair;
};
} expr_t;
expr_t *expr_integer(struct am *am, int64_t value);
expr_t *expr_symbol(struct am *am, const symbol_t *symbol);
expr_t *expr_str_symbol(struct am *am, const char *str);
expr_t *expr_empty_list(struct am *am);
expr_t *expr_pair(struct am *am, expr_t *car, expr_t *cdr);
expr_t *expr_prim_proc(struct am *am, prim_proc_t prim_proc);
#endif

View File

@@ -0,0 +1,15 @@
#ifndef MEMORY_STREAM_H
#define MEMORY_STREAM_H
#include "stream.h"
#include <stddef.h>
typedef struct {
stream_t stream;
const uint8_t *src, *end;
} memory_stream_t;
void memory_stream_init(memory_stream_t *s, const uint8_t *src, size_t size);
#endif

26
lib/include/parse.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef PARSE_H
#define PARSE_H
#include "am.h"
#include "token.h"
#define PARSE_MAX_DEPTH 128U
typedef enum {
PARSE_STATE_INIT,
PARSE_STATE_LIST,
PARSE_STATE_DONE,
PARSE_STATE_ERROR,
} parse_state_t;
typedef struct {
am_t *am;
store_t *store;
parse_state_t state;
parse_state_t *sp, stack[PARSE_MAX_DEPTH];
} parse_ctx_t;
void parse_init(am_t *am, parse_ctx_t *out);
parse_state_t parse_proc(parse_ctx_t *ctx, const token_t *token);
#endif

9
lib/include/prim.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef PRIM_H
#define PRIM_H
#include "am.h"
#include "expr.h"
void prim_load(am_t *am);
#endif

8
lib/include/print.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef PRINT_H
#define PRINT_H
#include "am.h"
size_t print(am_t *am, char *buffer, size_t buffer_size);
#endif

9
lib/include/read.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef READ_H
#define READ_H
#include "am.h"
#include "stream.h"
void read(am_t *am, stream_t *stream);
#endif

16
lib/include/repl.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef REPL_H
#define REPL_H
#include "am.h"
#define REPL_IO_BUFFER_SIZE 256U
typedef struct {
am_t am;
char io_buffer[REPL_IO_BUFFER_SIZE];
} repl_t;
void repl_init(repl_t *repl);
int repl_run(repl_t *repl);
#endif

18
lib/include/store.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef STORE_H
#define STORE_H
#include "expr.h"
#define STORE_SIZE 256U
typedef struct store {
expr_t *free;
expr_t buffer[STORE_SIZE];
} store_t;
struct am;
void store_init(struct am *am);
expr_t *store_alloc(struct am *am);
#endif

20
lib/include/stream.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef STREAM_H
#define STREAM_H
#include <stdint.h>
#define STREAM_GET_BYTE(stream, out) stream->get_byte(stream, out)
#define STREAM_PEEK_BYTE(stream, out) stream->peek_byte(stream, out)
typedef enum {
STREAM_STATUS_OK,
STREAM_STATUS_ERROR,
STREAM_STATUS_END,
} stream_status_t;
typedef struct stream {
stream_status_t (*get_byte)(struct stream *s, uint8_t *out);
stream_status_t (*peek_byte)(struct stream *s, uint8_t *out);
} stream_t;
#endif

26
lib/include/token.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef TOKEN_H
#define TOKEN_H
#include "expr.h"
#include "stream.h"
typedef enum {
TOKEN_TYPE_INTEGER,
TOKEN_TYPE_SYMBOL,
TOKEN_TYPE_OPEN_PAREN,
TOKEN_TYPE_CLOSE_PAREN,
} token_type_t;
typedef struct {
token_type_t type;
union {
int64_t integer;
symbol_t symbol;
};
} token_t;
typedef enum { TOKEN_OK, TOKEN_FAILED } token_status_t;
token_status_t token_read(stream_t *input, token_t *out);
#endif

34
lib/memory_stream.c Normal file
View File

@@ -0,0 +1,34 @@
#include "memory_stream.h"
#include <stdbool.h>
static stream_status_t proc_byte(stream_t *s, uint8_t *out, bool consume)
{
memory_stream_t *ss = (memory_stream_t *)s;
if (ss->src < ss->end) {
*out = *ss->src;
if (consume)
++ss->src;
return STREAM_STATUS_OK;
} else {
return STREAM_STATUS_END;
}
}
static stream_status_t get_byte(stream_t *s, uint8_t *out)
{
return proc_byte(s, out, true);
}
static stream_status_t peek_byte(stream_t *s, uint8_t *out)
{
return proc_byte(s, out, false);
}
void memory_stream_init(memory_stream_t *s, const uint8_t *mem, size_t size)
{
s->stream.get_byte = get_byte;
s->stream.peek_byte = peek_byte;
s->src = mem;
s->end = mem + size;
}

83
lib/parse.c Normal file
View File

@@ -0,0 +1,83 @@
#include "parse.h"
#include <assert.h>
#include <string.h>
void parse_init(am_t *am, parse_ctx_t *out)
{
out->am = am;
out->state = PARSE_STATE_INIT;
out->sp = out->stack + PARSE_MAX_DEPTH - 1;
}
static void push_state(parse_ctx_t *ctx, parse_state_t state)
{
assert(ctx->sp >= ctx->stack);
*ctx->sp-- = state;
}
static parse_state_t pop_state(parse_ctx_t *ctx)
{
assert(ctx->sp < ctx->stack + PARSE_MAX_DEPTH - 1);
return *++ctx->sp;
}
parse_state_t parse_proc(parse_ctx_t *ctx, const token_t *token)
{
switch (ctx->state) {
case PARSE_STATE_INIT:
switch (token->type) {
case TOKEN_TYPE_INTEGER:
AM_EXPR(ctx->am) = expr_integer(ctx->am, token->integer);
ctx->state = PARSE_STATE_DONE;
break;
case TOKEN_TYPE_SYMBOL:
AM_EXPR(ctx->am) = expr_symbol(ctx->am, &token->symbol);
ctx->state = PARSE_STATE_DONE;
break;
case TOKEN_TYPE_OPEN_PAREN:
push_state(ctx, PARSE_STATE_DONE);
AM_EXPR(ctx->am) = expr_empty_list(ctx->am);
ctx->state = PARSE_STATE_LIST;
break;
case TOKEN_TYPE_CLOSE_PAREN:
ctx->state = PARSE_STATE_ERROR;
break;
}
break;
case PARSE_STATE_LIST:
switch (token->type) {
case TOKEN_TYPE_INTEGER:
AM_VAL(ctx->am) = expr_integer(ctx->am, token->integer);
am_append(ctx->am, EXPR, VAL);
break;
case TOKEN_TYPE_SYMBOL:
AM_VAL(ctx->am) = expr_symbol(ctx->am, &token->symbol);
am_append(ctx->am, EXPR, VAL);
break;
case TOKEN_TYPE_OPEN_PAREN:
am_push(ctx->am, EXPR);
push_state(ctx, PARSE_STATE_LIST);
AM_EXPR(ctx->am) = expr_empty_list(ctx->am);
ctx->state = PARSE_STATE_LIST;
break;
case TOKEN_TYPE_CLOSE_PAREN:
ctx->state = pop_state(ctx);
if (ctx->state == PARSE_STATE_LIST) {
AM_VAL(ctx->am) = AM_EXPR(ctx->am);
am_pop(ctx->am, EXPR);
am_append(ctx->am, EXPR, VAL);
}
break;
}
break;
case PARSE_STATE_DONE:
case PARSE_STATE_ERROR:
break;
}
assert(ctx->state != PARSE_STATE_INIT);
return ctx->state;
}

74
lib/prim.c Normal file
View File

@@ -0,0 +1,74 @@
#include "prim.h"
#include "env.h"
#include <assert.h>
#define NELEMS(arr) (sizeof(arr) / sizeof(arr[0]))
typedef struct {
const char *name;
prim_proc_t prim_proc;
} prim_table_entry_t;
static void add(am_t *am)
{
assert(AM_ARGL(am));
int64_t total = 0;
for (expr_t *list = AM_ARGL(am); !list->is_atom; list = list->pair.cdr) {
assert(list->pair.car->is_atom);
assert(list->pair.car->atom.type == ATOM_TYPE_INTEGER);
total += list->pair.car->atom.integer;
}
AM_VAL(am) = expr_integer(am, total);
}
static void mul(am_t *am)
{
assert(AM_ARGL(am));
int64_t total = 1;
for (expr_t *list = AM_ARGL(am); !list->is_atom; list = list->pair.cdr) {
assert(list->pair.car->is_atom);
assert(list->pair.car->atom.type == ATOM_TYPE_INTEGER);
total *= list->pair.car->atom.integer;
}
AM_VAL(am) = expr_integer(am, total);
}
static void sub(am_t *am)
{
assert(AM_ARGL(am));
assert(!AM_ARGL(am)->is_atom);
assert(AM_ARGL(am)->pair.car->is_atom);
assert(AM_ARGL(am)->pair.car->atom.type == ATOM_TYPE_INTEGER);
int64_t total = AM_ARGL(am)->pair.car->atom.integer;
if (!AM_ARGL(am)->is_atom && AM_ARGL(am)->pair.cdr->is_atom) {
total *= -1;
} else {
for (expr_t *list = AM_ARGL(am)->pair.cdr; !list->is_atom;
list = list->pair.cdr) {
assert(list->pair.car->is_atom);
assert(list->pair.car->atom.type == ATOM_TYPE_INTEGER);
total -= list->pair.car->atom.integer;
}
}
AM_VAL(am) = expr_integer(am, total);
}
static const prim_table_entry_t prim_table[] = {
{ "+", add },
{ "*", mul },
{ "-", sub },
};
void prim_load(am_t *am)
{
for (unsigned i = 0; i < NELEMS(prim_table); ++i) {
AM_EXPR(am) = expr_str_symbol(am, prim_table[i].name);
AM_VAL(am) = expr_prim_proc(am, prim_table[i].prim_proc);
env_set(am);
}
}

32
lib/print.c Normal file
View File

@@ -0,0 +1,32 @@
#include "print.h"
#include <assert.h>
size_t print(am_t *am, char *buffer, size_t buffer_size)
{
assert(AM_VAL(am)->is_atom);
assert(AM_VAL(am)->atom.type == ATOM_TYPE_INTEGER);
size_t i = 0;
int64_t value = AM_VAL(am)->atom.integer;
if (value < 0) {
buffer[i++] = '-';
value *= -1;
}
int divisor = 10;
while (divisor <= value)
divisor *= 10;
do {
divisor /= 10;
const int digit = value / divisor;
value = value % divisor;
assert(i < buffer_size);
buffer[i++] = '0' + (char)digit;
} while (divisor > 1);
return i;
}

22
lib/read.c Normal file
View File

@@ -0,0 +1,22 @@
#include "read.h"
#include "parse.h"
#include "token.h"
#include <assert.h>
void read(am_t *am, stream_t *stream)
{
parse_ctx_t ctx;
token_t token;
parse_init(am, &ctx);
while (ctx.state != PARSE_STATE_DONE) {
const token_status_t token_status = token_read(stream, &token);
assert(token_status == TOKEN_OK);
parse_proc(&ctx, &token);
assert(ctx.state != PARSE_STATE_ERROR);
}
}

58
lib/repl.c Normal file
View File

@@ -0,0 +1,58 @@
#include "repl.h"
#include "eval.h"
#include "memory_stream.h"
#include "print.h"
#include "read.h"
#include <assert.h>
#include <stdio.h>
#define PROMPT "> "
static int read_line(char buffer[REPL_IO_BUFFER_SIZE])
{
for (unsigned i = 0; i < REPL_IO_BUFFER_SIZE; ++i) {
const int c = getchar();
switch (c) {
case EOF:
return EOF;
case '\n':
return (int)i;
default:
buffer[i] = (char)c;
break;
}
}
assert(false);
return -2;
}
void repl_init(repl_t *repl)
{
am_init(&repl->am);
}
int repl_run(repl_t *repl)
{
int len;
memory_stream_t input;
while (true) {
fputs(PROMPT, stdout);
len = read_line(repl->io_buffer);
if (len < 0) {
fputc('\n', stdout);
return 0;
}
memory_stream_init(&input, (const uint8_t *)repl->io_buffer, len);
read(&repl->am, (stream_t *)&input);
eval(&repl->am);
len = print(&repl->am, repl->io_buffer, REPL_IO_BUFFER_SIZE - 1);
repl->io_buffer[len] = '\n';
fwrite(repl->io_buffer, 1, len + 1, stdout);
}
}

16
lib/store.c Normal file
View File

@@ -0,0 +1,16 @@
#include "store.h"
#include "am.h"
#include <string.h>
void store_init(am_t *am)
{
memset(&am->store, 0, sizeof(store_t));
am->store.free = am->store.buffer;
}
expr_t *store_alloc(am_t *am)
{
return am->store.free++;
}

71
lib/token.c Normal file
View File

@@ -0,0 +1,71 @@
#include "token.h"
#include <assert.h>
#include <ctype.h>
#include <stdbool.h>
typedef enum {
STATE_INIT,
STATE_INTEGER,
STATE_SYMBOL,
STATE_FINISHED,
} state_t;
static bool is_delim(uint8_t byte)
{
return isspace(byte) || byte == '(' || byte == ')';
}
token_status_t token_read(stream_t *input, token_t *out)
{
state_t state = STATE_INIT;
uint8_t byte;
stream_status_t status;
while (state != STATE_FINISHED) {
status = STREAM_PEEK_BYTE(input, &byte);
if (status != STREAM_STATUS_OK
|| (state != STATE_INIT && is_delim(byte)))
break;
status = STREAM_GET_BYTE(input, &byte);
if (status != STREAM_STATUS_OK)
break;
switch (state) {
case STATE_INIT:
if (byte == '(') {
out->type = TOKEN_TYPE_OPEN_PAREN;
state = STATE_FINISHED;
} else if (byte == ')') {
out->type = TOKEN_TYPE_CLOSE_PAREN;
state = STATE_FINISHED;
} else if (isdigit(byte)) {
out->type = TOKEN_TYPE_INTEGER;
out->integer = byte - '0';
state = STATE_INTEGER;
} else if (!isspace(byte)) {
out->type = TOKEN_TYPE_SYMBOL;
out->symbol.buf[0] = byte;
out->symbol.len = 1;
state = STATE_SYMBOL;
}
break;
case STATE_INTEGER:
assert(isdigit(byte));
out->integer *= 10;
out->integer += byte - '0';
break;
case STATE_SYMBOL:
assert(out->symbol.len < MAX_SYMBOL_LEN);
out->symbol.buf[out->symbol.len++] = byte;
break;
case STATE_FINISHED:
break;
}
}
return state != STATE_INIT ? TOKEN_OK : TOKEN_FAILED;
}

4
scripts/build-and-test.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
cmake --build build
ctest --test-dir build --output-on-failure | grep -v :PASS

2
scripts/check-format.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
scripts/list-source-files.sh | xargs -n1 clang-format --dry-run --Werror

3
scripts/lint.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
scripts/list-source-files.sh \
| xargs -n1 clang-tidy -p build --warnings-as-errors='*'

6
scripts/list-source-files.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
find . \
-not \( -path './.git' -prune \) \
-not \( -path './build' -prune \) \
-not \( -path './dep' -prune \) \
-name '*.c' -o -name '*.h'

23
tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,23 @@
function(add_test_suites)
foreach (source ${ARGN})
string(REGEX REPLACE ".c$" "" name ${source})
add_executable(${name} ${source})
configure_target(${name})
target_link_libraries(${name} PRIVATE imp unity)
add_test(NAME ${name} COMMAND ${name})
endforeach()
endfunction()
add_test_suites(
am_tests.c
env_tests.c
eval_tests.c
expr_tests.c
integration_tests.c
parse_tests.c
prim_tests.c
print_tests.c
read_tests.c
store_tests.c
token_tests.c
)

154
tests/am_tests.c Normal file
View File

@@ -0,0 +1,154 @@
#include "am.h"
#include "unity.h"
#define CAR(expr) (expr->pair.car)
#define CDR(expr) (expr->pair.cdr)
#define CADR(expr) CAR(CDR(expr))
static am_t am;
static bool test_prim_proc_called;
static void test_prim_proc(am_t *am)
{
(void)am;
test_prim_proc_called = true;
}
void setUp(void)
{
am_init(&am);
test_prim_proc_called = false;
}
void tearDown(void)
{
}
static void test_expr_value_restored_after_push_modify_pop(void)
{
expr_t a, b;
am.regs[EXPR] = &a;
am_push(&am, EXPR);
am.regs[EXPR] = &b;
am_pop(&am, EXPR);
TEST_ASSERT_EQUAL(&a, am.regs[EXPR]);
}
static void test_argl_value_restored_after_push_modify_pop(void)
{
expr_t a, b;
am.regs[ARGL] = &a;
am_push(&am, ARGL);
am.regs[ARGL] = &b;
am_pop(&am, ARGL);
TEST_ASSERT_EQUAL(&a, am.regs[ARGL]);
}
static void test_append_val_42_to_empty_argl(void)
{
am.regs[ARGL] = expr_empty_list(&am);
am.regs[VAL] = expr_integer(&am, 42);
am_append(&am, ARGL, VAL);
TEST_ASSERT_FALSE(am.regs[ARGL]->is_atom);
TEST_ASSERT_NOT_NULL(CAR(am.regs[ARGL]));
TEST_ASSERT_TRUE(CAR(am.regs[ARGL])->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, CAR(am.regs[ARGL])->atom.type);
TEST_ASSERT_EQUAL(42, CAR(am.regs[ARGL])->atom.integer);
TEST_ASSERT_TRUE(CDR(am.regs[ARGL])->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_EMPTY_LIST, CDR(am.regs[ARGL])->atom.type);
}
static void test_append_unev_2_to_expr_list_1(void)
{
am.regs[EXPR]
= expr_pair(&am, expr_integer(&am, 1), expr_empty_list(&am));
am.regs[UNEV] = expr_integer(&am, 2);
am_append(&am, EXPR, UNEV);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_FALSE(am.regs[EXPR]->is_atom);
TEST_ASSERT_NOT_NULL(CAR(am.regs[EXPR]));
TEST_ASSERT_TRUE(CAR(am.regs[EXPR])->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, CAR(am.regs[EXPR])->atom.type);
TEST_ASSERT_EQUAL(1, CAR(am.regs[EXPR])->atom.integer);
TEST_ASSERT_NOT_NULL(CDR(am.regs[EXPR]));
TEST_ASSERT_FALSE(CDR(am.regs[EXPR])->is_atom);
TEST_ASSERT_NOT_NULL(CADR(am.regs[EXPR]));
TEST_ASSERT_TRUE(CADR(am.regs[EXPR])->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, CADR(am.regs[EXPR])->atom.type);
TEST_ASSERT_EQUAL(2, CADR(am.regs[EXPR])->atom.integer);
}
static void test_car_of_expr_pair_1_2(void)
{
am.regs[EXPR]
= expr_pair(&am, expr_integer(&am, 1), expr_integer(&am, 2));
am_car(&am, EXPR, EXPR);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_TRUE(am.regs[EXPR]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[EXPR]->atom.type);
TEST_ASSERT_EQUAL(1, am.regs[EXPR]->atom.integer);
}
static void test_cdr_of_expr_pair_1_2(void)
{
am.regs[EXPR]
= expr_pair(&am, expr_integer(&am, 1), expr_integer(&am, 2));
am_cdr(&am, EXPR, EXPR);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_TRUE(am.regs[EXPR]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[EXPR]->atom.type);
TEST_ASSERT_EQUAL(2, am.regs[EXPR]->atom.integer);
}
static void test_assign_expr_to_val(void)
{
am.regs[EXPR] = expr_integer(&am, 42);
am_assign(&am, VAL, EXPR);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_TRUE(am.regs[EXPR]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[EXPR]->atom.type);
TEST_ASSERT_EQUAL(42, am.regs[EXPR]->atom.integer);
}
static void test_call_prim_proc_val(void)
{
am.regs[VAL] = expr_prim_proc(&am, test_prim_proc);
am_call(&am, VAL);
TEST_ASSERT_TRUE(test_prim_proc_called);
}
static void test_call_prim_proc_expr(void)
{
am.regs[EXPR] = expr_prim_proc(&am, test_prim_proc);
am_call(&am, EXPR);
TEST_ASSERT_TRUE(test_prim_proc_called);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_expr_value_restored_after_push_modify_pop);
RUN_TEST(test_argl_value_restored_after_push_modify_pop);
RUN_TEST(test_append_val_42_to_empty_argl);
RUN_TEST(test_append_unev_2_to_expr_list_1);
RUN_TEST(test_car_of_expr_pair_1_2);
RUN_TEST(test_cdr_of_expr_pair_1_2);
RUN_TEST(test_assign_expr_to_val);
RUN_TEST(test_call_prim_proc_val);
RUN_TEST(test_call_prim_proc_expr);
return UNITY_END();
}

57
tests/env_tests.c Normal file
View File

@@ -0,0 +1,57 @@
#include "env.h"
#include "unity.h"
#include <string.h>
static am_t am;
void setUp(void)
{
am_init(&am);
}
void tearDown(void)
{
}
static void test_set_foo_to_42_then_fetch(void)
{
am.regs[EXPR] = expr_str_symbol(&am, "foo");
am.regs[VAL] = expr_integer(&am, 42);
env_set(&am);
am.regs[EXPR] = expr_str_symbol(&am, "foo");
am.regs[VAL] = NULL;
env_fetch(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(42, am.regs[VAL]->atom.integer);
}
static void test_update_foo_from_123_to_456_then_fetch(void)
{
am.regs[EXPR] = expr_str_symbol(&am, "foo");
am.regs[VAL] = expr_integer(&am, 123);
env_set(&am);
am.regs[VAL] = expr_integer(&am, 456);
env_set(&am);
am.regs[EXPR] = expr_str_symbol(&am, "foo");
am.regs[VAL] = NULL;
env_fetch(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(456, am.regs[VAL]->atom.integer);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_set_foo_to_42_then_fetch);
RUN_TEST(test_update_foo_from_123_to_456_then_fetch);
return UNITY_END();
}

126
tests/eval_tests.c Normal file
View File

@@ -0,0 +1,126 @@
#include "am.h"
#include "env.h"
#include "eval.h"
#include "unity.h"
static am_t am;
void test_prim_proc(am_t *am)
{
(void)am;
}
void setUp(void)
{
am_init(&am);
}
void tearDown(void)
{
}
static void test_42_self_evals(void)
{
am.regs[EXPR] = expr_integer(&am, 42);
eval(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(42, am.regs[VAL]->atom.integer);
}
static void test_empty_list_self_evals(void)
{
am.regs[EXPR] = expr_empty_list(&am);
eval(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_EMPTY_LIST, am.regs[VAL]->atom.type);
}
static void test_prim_proc_self_evals(void)
{
am.regs[EXPR] = expr_prim_proc(&am, test_prim_proc);
eval(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_PRIM_PROC, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(test_prim_proc, am.regs[VAL]->atom.prim_proc);
}
static void test_foo_evals_to_42_when_set_in_env(void)
{
am.regs[EXPR] = expr_str_symbol(&am, "foo");
am.regs[VAL] = expr_integer(&am, 42);
env_set(&am);
am.regs[VAL] = NULL;
eval(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(42, am.regs[VAL]->atom.integer);
}
static void test_add_1_2_3_evals_to_6(void)
{
am.regs[EXPR] = expr_pair(
&am, expr_str_symbol(&am, "+"),
expr_pair(
&am, expr_integer(&am, 1),
expr_pair(
&am, expr_integer(&am, 2),
expr_pair(
&am, expr_integer(&am, 3), expr_empty_list(&am)))));
eval(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(6, am.regs[VAL]->atom.integer);
}
static void test_add_1_mul_2_3_evals_to_7(void)
{
am.regs[EXPR] = expr_pair(
&am, expr_str_symbol(&am, "+"),
expr_pair(
&am, expr_integer(&am, 1),
expr_pair(
&am,
expr_pair(
&am, expr_str_symbol(&am, "*"),
expr_pair(
&am, expr_integer(&am, 2),
expr_pair(
&am, expr_integer(&am, 3),
expr_empty_list(&am)))),
expr_empty_list(&am))));
eval(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(7, am.regs[VAL]->atom.integer);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_42_self_evals);
RUN_TEST(test_empty_list_self_evals);
RUN_TEST(test_prim_proc_self_evals);
RUN_TEST(test_foo_evals_to_42_when_set_in_env);
RUN_TEST(test_add_1_2_3_evals_to_6);
RUN_TEST(test_add_1_mul_2_3_evals_to_7);
return UNITY_END();
}

123
tests/expr_tests.c Normal file
View File

@@ -0,0 +1,123 @@
#include "am.h"
#include "expr.h"
#include "unity.h"
static am_t am;
void test_prim_proc(am_t *am)
{
(void)am;
}
void setUp(void)
{
am_init(&am);
}
void tearDown(void)
{
}
static void test_expr_integer_123(void)
{
expr_t *expr = expr_integer(&am, 123);
TEST_ASSERT_NOT_NULL(expr);
TEST_ASSERT_TRUE(expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, expr->atom.type);
TEST_ASSERT_EQUAL(123, expr->atom.integer);
}
static void test_expr_integer_456(void)
{
expr_t *expr = expr_integer(&am, 456);
TEST_ASSERT_NOT_NULL(expr);
TEST_ASSERT_TRUE(expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, expr->atom.type);
TEST_ASSERT_EQUAL(456, expr->atom.integer);
}
static void test_expr_symbol_foo(void)
{
const symbol_t symbol = { .buf = "foo", .len = 3 };
expr_t *expr = expr_symbol(&am, &symbol);
TEST_ASSERT_NOT_NULL(expr);
TEST_ASSERT_TRUE(expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, expr->atom.type);
TEST_ASSERT_EQUAL(3, expr->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("foo", expr->atom.symbol.buf, 3);
}
static void test_expr_symbol_quux(void)
{
const symbol_t symbol = { .buf = "quux", .len = 4 };
expr_t *expr = expr_symbol(&am, &symbol);
TEST_ASSERT_NOT_NULL(expr);
TEST_ASSERT_TRUE(expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, expr->atom.type);
TEST_ASSERT_EQUAL(4, expr->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("quux", expr->atom.symbol.buf, 4);
}
static void test_expr_str_symbol_foo(void)
{
expr_t *expr = expr_str_symbol(&am, "foo");
TEST_ASSERT_NOT_NULL(expr);
TEST_ASSERT_TRUE(expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, expr->atom.type);
TEST_ASSERT_EQUAL(3, expr->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("foo", expr->atom.symbol.buf, 3);
}
static void test_expr_str_symbol_quux(void)
{
expr_t *expr = expr_str_symbol(&am, "quux");
TEST_ASSERT_NOT_NULL(expr);
TEST_ASSERT_TRUE(expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, expr->atom.type);
TEST_ASSERT_EQUAL(4, expr->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("quux", expr->atom.symbol.buf, 4);
}
static void test_expr_empty_list(void)
{
expr_t *expr = expr_empty_list(&am);
TEST_ASSERT_NOT_NULL(expr);
TEST_ASSERT_TRUE(expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_EMPTY_LIST, expr->atom.type);
}
static void test_expr_pair(void)
{
expr_t *car = expr_integer(&am, 123);
expr_t *cdr = expr_integer(&am, 456);
expr_t *expr = expr_pair(&am, car, cdr);
TEST_ASSERT_NOT_NULL(expr);
TEST_ASSERT_FALSE(expr->is_atom);
TEST_ASSERT_EQUAL(car, expr->pair.car);
TEST_ASSERT_EQUAL(cdr, expr->pair.cdr);
}
static void test_expr_prim_proc(void)
{
expr_t *expr = expr_prim_proc(&am, test_prim_proc);
TEST_ASSERT_NOT_NULL(expr);
TEST_ASSERT_TRUE(expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_PRIM_PROC, expr->atom.type);
TEST_ASSERT_EQUAL(test_prim_proc, expr->atom.prim_proc);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_expr_integer_123);
RUN_TEST(test_expr_integer_456);
RUN_TEST(test_expr_symbol_foo);
RUN_TEST(test_expr_symbol_quux);
RUN_TEST(test_expr_str_symbol_foo);
RUN_TEST(test_expr_str_symbol_quux);
RUN_TEST(test_expr_empty_list);
RUN_TEST(test_expr_pair);
RUN_TEST(test_expr_prim_proc);
return UNITY_END();
}

55
tests/integration_tests.c Normal file
View File

@@ -0,0 +1,55 @@
#include "eval.h"
#include "memory_stream.h"
#include "print.h"
#include "read.h"
#include "unity.h"
#include <string.h>
#define BUFFER_SIZE 256U
static am_t am;
static char buffer[BUFFER_SIZE];
static const char *read_eval_print(const char *input)
{
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
read(&am, (stream_t *)&stream);
eval(&am);
memset(buffer, 0, sizeof(buffer));
print(&am, buffer, BUFFER_SIZE - 1);
return buffer;
}
void setUp(void)
{
am_init(&am);
}
void tearDown(void)
{
}
static void test_unnested_arithmetic(void)
{
TEST_ASSERT_EQUAL_STRING("10", read_eval_print("(+ 1 2 3 4)"));
TEST_ASSERT_EQUAL_STRING("42", read_eval_print("(* 2 21)"));
TEST_ASSERT_EQUAL_STRING("64", read_eval_print("(- 100 36)"));
}
static void test_nested_arithmetic(void)
{
TEST_ASSERT_EQUAL_STRING("20", read_eval_print("(+ 1 (* 2 (+ 3 4)) 5)"));
TEST_ASSERT_EQUAL_STRING("-6", read_eval_print("(+ 1 (* 2 2 (- 3)) 5)"));
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_unnested_arithmetic);
RUN_TEST(test_nested_arithmetic);
return UNITY_END();
}

230
tests/parse_tests.c Normal file
View File

@@ -0,0 +1,230 @@
#include "parse.h"
#include "unity.h"
static am_t am;
static parse_ctx_t ctx;
#define NELEMS(arr) (sizeof(arr) / sizeof(arr[0]))
void setUp(void)
{
am_init(&am);
parse_init(&am, &ctx);
}
void tearDown(void)
{
}
static void test_integer_123(void)
{
const token_t token = { .type = TOKEN_TYPE_INTEGER, .integer = 123 };
const parse_state_t state = parse_proc(&ctx, &token);
TEST_ASSERT_EQUAL(PARSE_STATE_DONE, state);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_TRUE(am.regs[EXPR]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[EXPR]->atom.type);
TEST_ASSERT_EQUAL(123, am.regs[EXPR]->atom.integer);
}
static void test_integer_321(void)
{
const token_t token = { .type = TOKEN_TYPE_INTEGER, .integer = 321 };
const parse_state_t state = parse_proc(&ctx, &token);
TEST_ASSERT_EQUAL(PARSE_STATE_DONE, state);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_TRUE(am.regs[EXPR]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[EXPR]->atom.type);
TEST_ASSERT_EQUAL(321, am.regs[EXPR]->atom.integer);
}
static void test_symbol_foo(void)
{
const token_t token = {
.type = TOKEN_TYPE_SYMBOL,
.symbol = { .buf = "foo", .len = 3 },
};
const parse_state_t state = parse_proc(&ctx, &token);
TEST_ASSERT_EQUAL(PARSE_STATE_DONE, state);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_TRUE(am.regs[EXPR]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, am.regs[EXPR]->atom.type);
TEST_ASSERT_EQUAL(3, am.regs[EXPR]->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("foo", am.regs[EXPR]->atom.symbol.buf, 3);
}
static void test_symbol_quux(void)
{
const token_t token = {
.type = TOKEN_TYPE_SYMBOL,
.symbol = { .buf = "quux", .len = 4 },
};
const parse_state_t state = parse_proc(&ctx, &token);
TEST_ASSERT_EQUAL(PARSE_STATE_DONE, state);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_TRUE(am.regs[EXPR]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, am.regs[EXPR]->atom.type);
TEST_ASSERT_EQUAL(4, am.regs[EXPR]->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("quux", am.regs[EXPR]->atom.symbol.buf, 4);
}
static void test_open_paren_close_paren(void)
{
// ()
const token_t tokens[] = {
{ .type = TOKEN_TYPE_OPEN_PAREN },
{ .type = TOKEN_TYPE_CLOSE_PAREN },
};
parse_state_t state;
state = parse_proc(&ctx, tokens + 0);
TEST_ASSERT_EQUAL(PARSE_STATE_LIST, state);
state = parse_proc(&ctx, tokens + 1);
TEST_ASSERT_EQUAL(PARSE_STATE_DONE, state);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_TRUE(am.regs[EXPR]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_EMPTY_LIST, am.regs[EXPR]->atom.type);
}
static void test_open_paren_foo_42_close_paren(void)
{
// (foo 1) -> (foo . (1 . ()))
const token_t tokens[] = {
{ .type = TOKEN_TYPE_OPEN_PAREN },
{
.type = TOKEN_TYPE_SYMBOL,
.symbol = { .buf = "foo", .len = 3 },
},
{ .type = TOKEN_TYPE_INTEGER, .integer = 42 },
{ .type = TOKEN_TYPE_CLOSE_PAREN },
};
parse_state_t state;
for (unsigned i = 0; i < NELEMS(tokens) - 1; ++i) {
state = parse_proc(&ctx, tokens + i);
TEST_ASSERT_EQUAL(PARSE_STATE_LIST, state);
}
state = parse_proc(&ctx, tokens + NELEMS(tokens) - 1);
TEST_ASSERT_EQUAL(PARSE_STATE_DONE, state);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_FALSE(am.regs[EXPR]->is_atom);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.car);
TEST_ASSERT_TRUE(am.regs[EXPR]->pair.car->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, am.regs[EXPR]->pair.car->atom.type);
TEST_ASSERT_EQUAL(3, am.regs[EXPR]->pair.car->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY(
"foo", am.regs[EXPR]->pair.car->atom.symbol.buf, 3);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.cdr);
TEST_ASSERT_FALSE(am.regs[EXPR]->pair.cdr->is_atom);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.cdr->pair.car);
TEST_ASSERT_TRUE(am.regs[EXPR]->pair.cdr->pair.car->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_INTEGER, am.regs[EXPR]->pair.cdr->pair.car->atom.type);
TEST_ASSERT_EQUAL(42, am.regs[EXPR]->pair.cdr->pair.car->atom.integer);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.cdr->pair.cdr);
TEST_ASSERT_TRUE(am.regs[EXPR]->pair.cdr->pair.cdr->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_EMPTY_LIST, am.regs[EXPR]->pair.cdr->pair.cdr->atom.type);
}
static void test_open_paren_1_open_paren_2_close_paren_3_close_paren(void)
{
// (1 (2) 3) -> (1 . ((2 . ()) . (3 . ())))
const token_t tokens[] = {
{ .type = TOKEN_TYPE_OPEN_PAREN },
{ .type = TOKEN_TYPE_INTEGER, .integer = 1 },
{ .type = TOKEN_TYPE_OPEN_PAREN },
{ .type = TOKEN_TYPE_INTEGER, .integer = 2 },
{ .type = TOKEN_TYPE_CLOSE_PAREN },
{ .type = TOKEN_TYPE_INTEGER, .integer = 3 },
{ .type = TOKEN_TYPE_CLOSE_PAREN },
};
parse_state_t state;
for (unsigned i = 0; i < NELEMS(tokens) - 1; ++i) {
state = parse_proc(&ctx, tokens + i);
TEST_ASSERT_EQUAL(PARSE_STATE_LIST, state);
}
state = parse_proc(&ctx, tokens + NELEMS(tokens) - 1);
TEST_ASSERT_EQUAL(PARSE_STATE_DONE, state);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_FALSE(am.regs[EXPR]->is_atom);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.car);
TEST_ASSERT_TRUE(am.regs[EXPR]->pair.car->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[EXPR]->pair.car->atom.type);
TEST_ASSERT_EQUAL(1, am.regs[EXPR]->pair.car->atom.integer);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.cdr);
TEST_ASSERT_FALSE(am.regs[EXPR]->pair.cdr->is_atom);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.cdr->pair.car);
TEST_ASSERT_FALSE(am.regs[EXPR]->pair.cdr->pair.car->is_atom);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.cdr->pair.car->pair.car);
TEST_ASSERT_TRUE(am.regs[EXPR]->pair.cdr->pair.car->pair.car->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_INTEGER,
am.regs[EXPR]->pair.cdr->pair.car->pair.car->atom.type);
TEST_ASSERT_EQUAL(
2, am.regs[EXPR]->pair.cdr->pair.car->pair.car->atom.integer);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.cdr->pair.car->pair.cdr);
TEST_ASSERT_TRUE(am.regs[EXPR]->pair.cdr->pair.car->pair.cdr->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_EMPTY_LIST,
am.regs[EXPR]->pair.cdr->pair.car->pair.cdr->atom.type);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.cdr->pair.cdr);
TEST_ASSERT_FALSE(am.regs[EXPR]->pair.cdr->pair.cdr->is_atom);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.cdr->pair.cdr->pair.car);
TEST_ASSERT_TRUE(am.regs[EXPR]->pair.cdr->pair.cdr->pair.car->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_INTEGER,
am.regs[EXPR]->pair.cdr->pair.cdr->pair.car->atom.type);
TEST_ASSERT_EQUAL(
3, am.regs[EXPR]->pair.cdr->pair.cdr->pair.car->atom.integer);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]->pair.cdr->pair.cdr->pair.cdr);
TEST_ASSERT_TRUE(am.regs[EXPR]->pair.cdr->pair.cdr->pair.cdr->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_EMPTY_LIST,
am.regs[EXPR]->pair.cdr->pair.cdr->pair.cdr->atom.type);
}
static void test_close_paren(void)
{
const token_t token = { .type = TOKEN_TYPE_CLOSE_PAREN };
const parse_state_t state = parse_proc(&ctx, &token);
TEST_ASSERT_EQUAL(PARSE_STATE_ERROR, state);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_integer_123);
RUN_TEST(test_integer_321);
RUN_TEST(test_symbol_foo);
RUN_TEST(test_symbol_quux);
RUN_TEST(test_open_paren_close_paren);
RUN_TEST(test_open_paren_foo_42_close_paren);
RUN_TEST(test_open_paren_1_open_paren_2_close_paren_3_close_paren);
RUN_TEST(test_close_paren);
return UNITY_END();
}

141
tests/prim_tests.c Normal file
View File

@@ -0,0 +1,141 @@
#include "env.h"
#include "prim.h"
#include "unity.h"
static am_t am;
void setUp(void)
{
am_init(&am);
}
void tearDown(void)
{
}
static void test_add_empty_list_is_0(void)
{
am.regs[EXPR] = expr_str_symbol(&am, "+");
env_fetch(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_PRIM_PROC, am.regs[VAL]->atom.type);
am.regs[ARGL] = expr_empty_list(&am);
am.regs[VAL]->atom.prim_proc(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(0, am.regs[VAL]->atom.integer);
}
static void test_add_1_2_3_is_6(void)
{
am.regs[EXPR] = expr_str_symbol(&am, "+");
env_fetch(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_PRIM_PROC, am.regs[VAL]->atom.type);
am.regs[ARGL] = expr_pair(
&am, expr_integer(&am, 1),
expr_pair(
&am, expr_integer(&am, 2),
expr_pair(&am, expr_integer(&am, 3), expr_empty_list(&am))));
am.regs[VAL]->atom.prim_proc(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(6, am.regs[VAL]->atom.integer);
}
static void test_mul_empty_list_is_1(void)
{
am.regs[EXPR] = expr_str_symbol(&am, "*");
env_fetch(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_PRIM_PROC, am.regs[VAL]->atom.type);
am.regs[ARGL] = expr_empty_list(&am);
am.regs[VAL]->atom.prim_proc(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(1, am.regs[VAL]->atom.integer);
}
static void test_mul_2_3_4_is_24(void)
{
am.regs[EXPR] = expr_str_symbol(&am, "*");
env_fetch(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_PRIM_PROC, am.regs[VAL]->atom.type);
am.regs[ARGL] = expr_pair(
&am, expr_integer(&am, 2),
expr_pair(
&am, expr_integer(&am, 3),
expr_pair(&am, expr_integer(&am, 4), expr_empty_list(&am))));
am.regs[VAL]->atom.prim_proc(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(24, am.regs[VAL]->atom.integer);
}
static void test_sub_1_is_minus_1(void)
{
am.regs[EXPR] = expr_str_symbol(&am, "-");
env_fetch(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_PRIM_PROC, am.regs[VAL]->atom.type);
am.regs[ARGL]
= expr_pair(&am, expr_integer(&am, 1), expr_empty_list(&am));
am.regs[VAL]->atom.prim_proc(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(-1, am.regs[VAL]->atom.integer);
}
static void test_sub_5_4_3_is_minus_2(void)
{
am.regs[EXPR] = expr_str_symbol(&am, "-");
env_fetch(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_PRIM_PROC, am.regs[VAL]->atom.type);
am.regs[ARGL] = expr_pair(
&am, expr_integer(&am, 5),
expr_pair(
&am, expr_integer(&am, 4),
expr_pair(&am, expr_integer(&am, 3), expr_empty_list(&am))));
am.regs[VAL]->atom.prim_proc(&am);
TEST_ASSERT_NOT_NULL(am.regs[VAL]);
TEST_ASSERT_TRUE(am.regs[VAL]->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.regs[VAL]->atom.type);
TEST_ASSERT_EQUAL(-2, am.regs[VAL]->atom.integer);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_add_empty_list_is_0);
RUN_TEST(test_add_1_2_3_is_6);
RUN_TEST(test_mul_empty_list_is_1);
RUN_TEST(test_mul_2_3_4_is_24);
RUN_TEST(test_sub_1_is_minus_1);
RUN_TEST(test_sub_5_4_3_is_minus_2);
return UNITY_END();
}

70
tests/print_tests.c Normal file
View File

@@ -0,0 +1,70 @@
#include "print.h"
#include "unity.h"
#include <string.h>
#define BUFFER_SIZE 256U
static am_t am;
static char buffer[BUFFER_SIZE];
void setUp(void)
{
memset(buffer, 0, BUFFER_SIZE);
am_init(&am);
}
void tearDown(void)
{
}
static void test_integer_5(void)
{
am.regs[VAL] = expr_integer(&am, 5);
const size_t len = print(&am, buffer, BUFFER_SIZE);
TEST_ASSERT_EQUAL(1, len);
TEST_ASSERT_EQUAL_MEMORY("5", buffer, 1);
}
static void test_integer_1234(void)
{
am.regs[VAL] = expr_integer(&am, 1234);
const size_t len = print(&am, buffer, BUFFER_SIZE);
TEST_ASSERT_EQUAL(4, len);
TEST_ASSERT_EQUAL_MEMORY("1234", buffer, 4);
}
static void test_integer_0(void)
{
am.regs[VAL] = expr_integer(&am, 0);
const size_t len = print(&am, buffer, BUFFER_SIZE);
TEST_ASSERT_EQUAL(1, len);
TEST_ASSERT_EQUAL_MEMORY("0", buffer, 1);
}
static void test_integer_10(void)
{
am.regs[VAL] = expr_integer(&am, 10);
const size_t len = print(&am, buffer, BUFFER_SIZE);
TEST_ASSERT_EQUAL(2, len);
TEST_ASSERT_EQUAL_MEMORY("10", buffer, 2);
}
static void test_integer_minus_4321(void)
{
am.regs[VAL] = expr_integer(&am, -4321);
const size_t len = print(&am, buffer, BUFFER_SIZE);
TEST_ASSERT_EQUAL(5, len);
TEST_ASSERT_EQUAL_MEMORY("-4321", buffer, 5);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_integer_5);
RUN_TEST(test_integer_1234);
RUN_TEST(test_integer_0);
RUN_TEST(test_integer_10);
RUN_TEST(test_integer_minus_4321);
return UNITY_END();
}

99
tests/read_tests.c Normal file
View File

@@ -0,0 +1,99 @@
#include "memory_stream.h"
#include "read.h"
#include "unity.h"
#include <string.h>
#define CAR(expr) (expr->pair.car)
#define CDR(expr) (expr->pair.cdr)
#define CADR(expr) CAR(CDR(expr))
#define CDDR(expr) CDR(CDR(expr))
#define CADDR(expr) CAR(CDDR(expr))
#define CDDDR(expr) CDR(CDDR(expr))
#define CAADDR(expr) CAR(CADDR(expr))
#define CDADDR(expr) CDR(CADDR(expr))
#define CADADDR(expr) CAR(CDADDR(expr))
#define CDDADDR(expr) CDR(CDADDR(expr))
#define CADDADDR(expr) CAR(CDDADDR(expr))
#define CDDDADDR(expr) CDR(CDDADDR(expr))
static am_t am;
void setUp(void)
{
am_init(&am);
}
void tearDown(void)
{
}
static void test_nested_expression(void)
{
const char *input = "(+ 1 (* 2 3))";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
read(&am, (stream_t *)&stream);
TEST_ASSERT_NOT_NULL(am.regs[EXPR]);
TEST_ASSERT_FALSE(am.regs[EXPR]->is_atom);
TEST_ASSERT_NOT_NULL(CAR(am.regs[EXPR]));
TEST_ASSERT_TRUE(CAR(am.regs[EXPR])->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, CAR(am.regs[EXPR])->atom.type);
TEST_ASSERT_EQUAL(1, CAR(am.regs[EXPR])->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("+", CAR(am.regs[EXPR])->atom.symbol.buf, 1);
TEST_ASSERT_NOT_NULL(CDR(am.regs[EXPR]));
TEST_ASSERT_FALSE(CDR(am.regs[EXPR])->is_atom);
TEST_ASSERT_NOT_NULL(CADR(am.regs[EXPR]));
TEST_ASSERT_TRUE(CADR(am.regs[EXPR])->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, CADR(am.regs[EXPR])->atom.type);
TEST_ASSERT_EQUAL(1, CADR(am.regs[EXPR])->atom.integer);
TEST_ASSERT_NOT_NULL(CDDR(am.regs[EXPR]));
TEST_ASSERT_FALSE(CDDR(am.regs[EXPR])->is_atom);
TEST_ASSERT_NOT_NULL(CADDR(am.regs[EXPR]));
TEST_ASSERT_FALSE(CADDR(am.regs[EXPR])->is_atom);
TEST_ASSERT_NOT_NULL(CAADDR(am.regs[EXPR]));
TEST_ASSERT_TRUE(CAADDR(am.regs[EXPR])->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, CAADDR(am.regs[EXPR])->atom.type);
TEST_ASSERT_EQUAL(1, CAADDR(am.regs[EXPR])->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("*", CAADDR(am.regs[EXPR])->atom.symbol.buf, 1);
TEST_ASSERT_NOT_NULL(CDADDR(am.regs[EXPR]));
TEST_ASSERT_FALSE(CDADDR(am.regs[EXPR])->is_atom);
TEST_ASSERT_NOT_NULL(CADADDR(am.regs[EXPR]));
TEST_ASSERT_TRUE(CADADDR(am.regs[EXPR])->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, CADADDR(am.regs[EXPR])->atom.type);
TEST_ASSERT_EQUAL(2, CADADDR(am.regs[EXPR])->atom.integer);
TEST_ASSERT_NOT_NULL(CDDADDR(am.regs[EXPR]));
TEST_ASSERT_FALSE(CDDADDR(am.regs[EXPR])->is_atom);
TEST_ASSERT_NOT_NULL(CADDADDR(am.regs[EXPR]));
TEST_ASSERT_TRUE(CADDADDR(am.regs[EXPR])->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, CADDADDR(am.regs[EXPR])->atom.type);
TEST_ASSERT_EQUAL(3, CADDADDR(am.regs[EXPR])->atom.integer);
TEST_ASSERT_NOT_NULL(CDDDADDR(am.regs[EXPR]));
TEST_ASSERT_TRUE(CDDDADDR(am.regs[EXPR])->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_EMPTY_LIST, CDDDADDR(am.regs[EXPR])->atom.type);
TEST_ASSERT_NOT_NULL(CDDDR(am.regs[EXPR]));
TEST_ASSERT_TRUE(CDDDR(am.regs[EXPR])->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_EMPTY_LIST, CDDDR(am.regs[EXPR])->atom.type);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_nested_expression);
return UNITY_END();
}

34
tests/store_tests.c Normal file
View File

@@ -0,0 +1,34 @@
#include "am.h"
#include "unity.h"
static am_t am;
void setUp(void)
{
am_init(&am);
}
void tearDown(void)
{
}
static void test_alloc_returns_non_null_after_init(void)
{
const expr_t *const expr = store_alloc(&am);
TEST_ASSERT_NOT_NULL(expr);
}
static void test_two_calls_to_alloc_return_distinct(void)
{
const expr_t *const a = store_alloc(&am);
const expr_t *const b = store_alloc(&am);
TEST_ASSERT_NOT_EQUAL(a, b);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_alloc_returns_non_null_after_init);
RUN_TEST(test_two_calls_to_alloc_return_distinct);
return UNITY_END();
}

329
tests/token_tests.c Normal file
View File

@@ -0,0 +1,329 @@
#include "memory_stream.h"
#include "token.h"
#include "unity.h"
#include <string.h>
void setUp(void)
{
}
void tearDown(void)
{
}
static void test_123(void)
{
const char *input = "123";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_INTEGER, token.type);
TEST_ASSERT_EQUAL(123, token.integer);
}
static void test_321(void)
{
const char *input = "321";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_INTEGER, token.type);
TEST_ASSERT_EQUAL(321, token.integer);
}
static void test_foo(void)
{
const char *input = "foo";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_SYMBOL, token.type);
TEST_ASSERT_EQUAL(3, token.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("foo", token.symbol.buf, 3);
}
static void test_quux(void)
{
const char *input = "quux";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_SYMBOL, token.type);
TEST_ASSERT_EQUAL(4, token.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("quux", token.symbol.buf, 3);
}
static void test_space_space_space_456(void)
{
const char *input = " 456";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_INTEGER, token.type);
TEST_ASSERT_EQUAL(456, token.integer);
}
static void test_tab_tab_bar(void)
{
const char *input = "\t\tbar";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_SYMBOL, token.type);
TEST_ASSERT_EQUAL(3, token.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("bar", token.symbol.buf, 3);
}
static void test_12_space_34(void)
{
const char *input = "12 34";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_INTEGER, token.type);
TEST_ASSERT_EQUAL(12, token.integer);
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_INTEGER, token.type);
TEST_ASSERT_EQUAL(34, token.integer);
}
static void test_12_tab_34(void)
{
const char *input = "12\t34";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_INTEGER, token.type);
TEST_ASSERT_EQUAL(12, token.integer);
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_INTEGER, token.type);
TEST_ASSERT_EQUAL(34, token.integer);
}
static void test_foo_space_bar(void)
{
const char *input = "foo bar";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_SYMBOL, token.type);
TEST_ASSERT_EQUAL(3, token.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("foo", token.symbol.buf, 3);
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_SYMBOL, token.type);
TEST_ASSERT_EQUAL(3, token.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("bar", token.symbol.buf, 3);
}
static void test_foo_tab_bar(void)
{
const char *input = "foo\tbar";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_SYMBOL, token.type);
TEST_ASSERT_EQUAL(3, token.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("foo", token.symbol.buf, 3);
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_SYMBOL, token.type);
TEST_ASSERT_EQUAL(3, token.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("bar", token.symbol.buf, 3);
}
static void test_open_paren(void)
{
const char *input = "(";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_OPEN_PAREN, token.type);
}
static void test_close_paren(void)
{
const char *input = ")";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_CLOSE_PAREN, token.type);
}
static void test_42_open_paren(void)
{
const char *input = "42(";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_INTEGER, token.type);
TEST_ASSERT_EQUAL(42, token.integer);
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_OPEN_PAREN, token.type);
}
static void test_42_close_paren(void)
{
const char *input = "42)";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_INTEGER, token.type);
TEST_ASSERT_EQUAL(42, token.integer);
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_CLOSE_PAREN, token.type);
}
static void test_open_paren_foo_close_paren(void)
{
const char *input = "(foo)";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_OPEN_PAREN, token.type);
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_SYMBOL, token.type);
TEST_ASSERT_EQUAL(3, token.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("foo", token.symbol.buf, 3);
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_OK, status);
TEST_ASSERT_EQUAL(TOKEN_TYPE_CLOSE_PAREN, token.type);
}
static void test_empty_input(void)
{
const char *input = "";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_FAILED, status);
}
static void test_space_tab(void)
{
const char *input = " \t";
memory_stream_t stream;
memory_stream_init(&stream, (const uint8_t *)input, strlen(input));
token_t token;
token_status_t status;
status = token_read((stream_t *)&stream, &token);
TEST_ASSERT_EQUAL(TOKEN_FAILED, status);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_123);
RUN_TEST(test_321);
RUN_TEST(test_foo);
RUN_TEST(test_quux);
RUN_TEST(test_space_space_space_456);
RUN_TEST(test_tab_tab_bar);
RUN_TEST(test_12_space_34);
RUN_TEST(test_12_tab_34);
RUN_TEST(test_foo_space_bar);
RUN_TEST(test_foo_tab_bar);
RUN_TEST(test_open_paren);
RUN_TEST(test_close_paren);
RUN_TEST(test_42_open_paren);
RUN_TEST(test_42_close_paren);
RUN_TEST(test_open_paren_foo_close_paren);
RUN_TEST(test_empty_input);
RUN_TEST(test_space_tab);
return UNITY_END();
}