Compare commits

...

10 Commits

29 changed files with 1392 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/

25
CMakeLists.txt Normal file
View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.15)
project(imp LANGUAGES C)
option(TESTS "Build tests" 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()

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.

10
lib/CMakeLists.txt Normal file
View File

@@ -0,0 +1,10 @@
add_library(imp
am.c
env.c
memory_stream.c
parse.c
store.c
token.c
)
target_include_directories(imp PUBLIC include)
configure_target(imp)

22
lib/am.c Normal file
View File

@@ -0,0 +1,22 @@
#include "am.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;
}
void am_push(am_t *am)
{
assert(am->sp >= am->stack);
*am->sp-- = am->expr;
}
void am_pop(am_t *am)
{
assert(am->sp < am->stack + AM_STACK_SIZE - 1);
am->expr = *++am->sp;
}

80
lib/env.c Normal file
View File

@@ -0,0 +1,80 @@
#include "env.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 != NULL);
assert(am->expr->is_atom);
assert(am->expr->atom.type == ATOM_TYPE_SYMBOL);
if (am->env->is_atom) {
assert(am->env->atom.type == ATOM_TYPE_EMPTY_LIST);
*found = false;
return &am->env;
}
expr_t *prev;
for (expr_t *list = am->env; !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->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, store_t *store)
{
am->env = store_alloc(store);
am->env->is_atom = true;
am->env->atom.type = ATOM_TYPE_EMPTY_LIST;
}
void env_fetch(am_t *am)
{
bool found;
expr_t *val = *lookup(am, &found);
am->val = found ? val : NULL;
}
void env_set(am_t *am, store_t *store)
{
bool found;
expr_t **loc = lookup(am, &found);
if (found) {
*loc = am->val;
} else {
(*loc)->is_atom = false;
(*loc)->pair.cdr = store_alloc(store);
(*loc)->pair.cdr->is_atom = true;
(*loc)->pair.cdr->atom.type = ATOM_TYPE_EMPTY_LIST;
expr_t *entry = (*loc)->pair.car = store_alloc(store);
entry->pair.car = am->expr;
entry->pair.cdr = am->val;
}
}

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

@@ -0,0 +1,17 @@
#ifndef AM_H
#define AM_H
#include "expr.h"
#define AM_STACK_SIZE 128U
typedef struct {
expr_t *env, *expr, *val;
expr_t **sp, *stack[AM_STACK_SIZE];
} am_t;
void am_init(am_t *am);
void am_push(am_t *am);
void am_pop(am_t *am);
#endif

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

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

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

@@ -0,0 +1,43 @@
#ifndef EXPR_H
#define EXPR_H
#define MAX_SYMBOL_LEN 32U
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct {
char buf[MAX_SYMBOL_LEN];
size_t len;
} symbol_t;
typedef enum {
ATOM_TYPE_EMPTY_LIST,
ATOM_TYPE_INTEGER,
ATOM_TYPE_SYMBOL,
} atom_type_t;
typedef struct {
atom_type_t type;
union {
int64_t integer;
symbol_t symbol;
};
} 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;
#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

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

@@ -0,0 +1,27 @@
#ifndef PARSE_H
#define PARSE_H
#include "am.h"
#include "store.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, store_t *store, parse_ctx_t *out);
parse_state_t parse_proc(parse_ctx_t *ctx, const token_t *token);
#endif

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

@@ -0,0 +1,16 @@
#ifndef STORE_H
#define STORE_H
#include "expr.h"
#define STORE_SIZE 256U
typedef struct {
expr_t *free;
expr_t buffer[STORE_SIZE];
} store_t;
void store_init(store_t *store);
expr_t *store_alloc(store_t *store);
#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;
}

119
lib/parse.c Normal file
View File

@@ -0,0 +1,119 @@
#include "parse.h"
#include <assert.h>
#include <string.h>
void parse_init(am_t *am, store_t *store, parse_ctx_t *out)
{
out->am = am;
out->store = store;
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;
}
static void load_integer(parse_ctx_t *ctx, expr_t **expr, int64_t integer)
{
*expr = store_alloc(ctx->store);
(*expr)->is_atom = true;
(*expr)->atom.type = ATOM_TYPE_INTEGER;
(*expr)->atom.integer = integer;
}
static void
load_symbol(parse_ctx_t *ctx, expr_t **expr, const symbol_t *symbol)
{
*expr = store_alloc(ctx->store);
(*expr)->is_atom = true;
(*expr)->atom.type = ATOM_TYPE_SYMBOL;
memcpy(&(*expr)->atom.symbol, symbol, sizeof(symbol_t));
}
static expr_t **append(parse_ctx_t *ctx, expr_t *expr)
{
while (!expr->is_atom)
expr = expr->pair.cdr;
assert(expr->atom.type == ATOM_TYPE_EMPTY_LIST);
expr->is_atom = false;
expr->pair.cdr = store_alloc(ctx->store);
expr->pair.cdr->is_atom = true;
expr->pair.cdr->atom.type = ATOM_TYPE_EMPTY_LIST;
return &expr->pair.car;
}
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:
load_integer(ctx, &ctx->am->expr, token->integer);
ctx->state = PARSE_STATE_DONE;
break;
case TOKEN_TYPE_SYMBOL:
load_symbol(ctx, &ctx->am->expr, &token->symbol);
ctx->state = PARSE_STATE_DONE;
break;
case TOKEN_TYPE_OPEN_PAREN:
push_state(ctx, PARSE_STATE_DONE);
ctx->am->expr = store_alloc(ctx->store);
ctx->am->expr->is_atom = true;
ctx->am->expr->atom.type = ATOM_TYPE_EMPTY_LIST;
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) {
expr_t **end_car;
case TOKEN_TYPE_INTEGER:
end_car = append(ctx, ctx->am->expr);
load_integer(ctx, end_car, token->integer);
break;
case TOKEN_TYPE_SYMBOL:
end_car = append(ctx, ctx->am->expr);
load_symbol(ctx, end_car, &token->symbol);
break;
case TOKEN_TYPE_OPEN_PAREN:
am_push(ctx->am);
push_state(ctx, PARSE_STATE_LIST);
ctx->am->expr = store_alloc(ctx->store);
ctx->am->expr->is_atom = true;
ctx->am->expr->atom.type = ATOM_TYPE_EMPTY_LIST;
ctx->state = PARSE_STATE_LIST;
break;
case TOKEN_TYPE_CLOSE_PAREN:
ctx->state = pop_state(ctx);
if (ctx->state == PARSE_STATE_LIST) {
expr_t *expr = ctx->am->expr;
am_pop(ctx->am);
end_car = append(ctx, ctx->am->expr);
*end_car = expr;
}
break;
}
break;
case PARSE_STATE_DONE:
case PARSE_STATE_ERROR:
break;
}
assert(ctx->state != PARSE_STATE_INIT);
return ctx->state;
}

14
lib/store.c Normal file
View File

@@ -0,0 +1,14 @@
#include "store.h"
#include <string.h>
void store_init(store_t *store)
{
memset(store, 0, sizeof(store_t));
store->free = store->buffer;
}
expr_t *store_alloc(store_t *store)
{
return 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'

17
tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,17 @@
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
parse_tests.c
store_tests.c
token_tests.c
)

30
tests/am_tests.c Normal file
View File

@@ -0,0 +1,30 @@
#include "am.h"
#include "unity.h"
static am_t am;
void setUp(void)
{
am_init(&am);
}
void tearDown(void)
{
}
static void test_expr_value_restored_after_push_modify_pop(void)
{
expr_t a, b;
am.expr = &a;
am_push(&am);
am.expr = &b;
am_pop(&am);
TEST_ASSERT_EQUAL(&a, am.expr);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_expr_value_restored_after_push_modify_pop);
return UNITY_END();
}

78
tests/env_tests.c Normal file
View File

@@ -0,0 +1,78 @@
#include "env.h"
#include "unity.h"
#include <string.h>
static am_t am;
static store_t store;
static expr_t *integer(int64_t value)
{
expr_t *expr = store_alloc(&store);
expr->is_atom = true;
expr->atom.type = ATOM_TYPE_INTEGER;
expr->atom.integer = value;
return expr;
}
static expr_t *symbol(const char *s)
{
expr_t *expr = store_alloc(&store);
expr->is_atom = true;
expr->atom.type = ATOM_TYPE_SYMBOL;
memcpy(expr->atom.symbol.buf, s, strlen(s));
return expr;
}
void setUp(void)
{
am_init(&am);
store_init(&store);
env_init(&am, &store);
}
void tearDown(void)
{
}
static void test_set_foo_to_42_then_fetch(void)
{
am.expr = symbol("foo");
am.val = integer(42);
env_set(&am, &store);
am.expr = symbol("foo");
am.val = NULL;
env_fetch(&am);
TEST_ASSERT_NOT_NULL(am.val);
TEST_ASSERT_TRUE(am.val->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.val->atom.type);
TEST_ASSERT_EQUAL(42, am.val->atom.integer);
}
static void test_update_foo_from_123_to_456_then_fetch(void)
{
am.expr = symbol("foo");
am.val = integer(123);
env_set(&am, &store);
am.val = integer(456);
env_set(&am, &store);
am.expr = symbol("foo");
am.val = NULL;
env_fetch(&am);
TEST_ASSERT_NOT_NULL(am.val);
TEST_ASSERT_TRUE(am.val->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.val->atom.type);
TEST_ASSERT_EQUAL(456, am.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();
}

229
tests/parse_tests.c Normal file
View File

@@ -0,0 +1,229 @@
#include "parse.h"
#include "unity.h"
static store_t store;
static am_t am;
static parse_ctx_t ctx;
#define NELEMS(arr) (sizeof(arr) / sizeof(arr[0]))
void setUp(void)
{
store_init(&store);
am_init(&am);
parse_init(&am, &store, &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.expr);
TEST_ASSERT_TRUE(am.expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.expr->atom.type);
TEST_ASSERT_EQUAL(123, am.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.expr);
TEST_ASSERT_TRUE(am.expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.expr->atom.type);
TEST_ASSERT_EQUAL(321, am.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.expr);
TEST_ASSERT_TRUE(am.expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, am.expr->atom.type);
TEST_ASSERT_EQUAL(3, am.expr->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("foo", am.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.expr);
TEST_ASSERT_TRUE(am.expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, am.expr->atom.type);
TEST_ASSERT_EQUAL(4, am.expr->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("quux", am.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.expr);
TEST_ASSERT_TRUE(am.expr->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_EMPTY_LIST, am.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.expr);
TEST_ASSERT_FALSE(am.expr->is_atom);
TEST_ASSERT_NOT_NULL(am.expr->pair.car);
TEST_ASSERT_TRUE(am.expr->pair.car->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_SYMBOL, am.expr->pair.car->atom.type);
TEST_ASSERT_EQUAL(3, am.expr->pair.car->atom.symbol.len);
TEST_ASSERT_EQUAL_MEMORY("foo", am.expr->pair.car->atom.symbol.buf, 3);
TEST_ASSERT_NOT_NULL(am.expr->pair.cdr);
TEST_ASSERT_FALSE(am.expr->pair.cdr->is_atom);
TEST_ASSERT_NOT_NULL(am.expr->pair.cdr->pair.car);
TEST_ASSERT_TRUE(am.expr->pair.cdr->pair.car->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_INTEGER, am.expr->pair.cdr->pair.car->atom.type);
TEST_ASSERT_EQUAL(42, am.expr->pair.cdr->pair.car->atom.integer);
TEST_ASSERT_NOT_NULL(am.expr->pair.cdr->pair.cdr);
TEST_ASSERT_TRUE(am.expr->pair.cdr->pair.cdr->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_EMPTY_LIST, am.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.expr);
TEST_ASSERT_FALSE(am.expr->is_atom);
TEST_ASSERT_NOT_NULL(am.expr->pair.car);
TEST_ASSERT_TRUE(am.expr->pair.car->is_atom);
TEST_ASSERT_EQUAL(ATOM_TYPE_INTEGER, am.expr->pair.car->atom.type);
TEST_ASSERT_EQUAL(1, am.expr->pair.car->atom.integer);
TEST_ASSERT_NOT_NULL(am.expr->pair.cdr);
TEST_ASSERT_FALSE(am.expr->pair.cdr->is_atom);
TEST_ASSERT_NOT_NULL(am.expr->pair.cdr->pair.car);
TEST_ASSERT_FALSE(am.expr->pair.cdr->pair.car->is_atom);
TEST_ASSERT_NOT_NULL(am.expr->pair.cdr->pair.car->pair.car);
TEST_ASSERT_TRUE(am.expr->pair.cdr->pair.car->pair.car->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_INTEGER, am.expr->pair.cdr->pair.car->pair.car->atom.type);
TEST_ASSERT_EQUAL(
2, am.expr->pair.cdr->pair.car->pair.car->atom.integer);
TEST_ASSERT_NOT_NULL(am.expr->pair.cdr->pair.car->pair.cdr);
TEST_ASSERT_TRUE(am.expr->pair.cdr->pair.car->pair.cdr->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_EMPTY_LIST,
am.expr->pair.cdr->pair.car->pair.cdr->atom.type);
TEST_ASSERT_NOT_NULL(am.expr->pair.cdr->pair.cdr);
TEST_ASSERT_FALSE(am.expr->pair.cdr->pair.cdr->is_atom);
TEST_ASSERT_NOT_NULL(am.expr->pair.cdr->pair.cdr->pair.car);
TEST_ASSERT_TRUE(am.expr->pair.cdr->pair.cdr->pair.car->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_INTEGER, am.expr->pair.cdr->pair.cdr->pair.car->atom.type);
TEST_ASSERT_EQUAL(
3, am.expr->pair.cdr->pair.cdr->pair.car->atom.integer);
TEST_ASSERT_NOT_NULL(am.expr->pair.cdr->pair.cdr->pair.cdr);
TEST_ASSERT_TRUE(am.expr->pair.cdr->pair.cdr->pair.cdr->is_atom);
TEST_ASSERT_EQUAL(
ATOM_TYPE_EMPTY_LIST,
am.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();
}

34
tests/store_tests.c Normal file
View File

@@ -0,0 +1,34 @@
#include "store.h"
#include "unity.h"
static store_t store;
void setUp(void)
{
store_init(&store);
}
void tearDown(void)
{
}
static void test_alloc_returns_non_null_after_init(void)
{
const expr_t *const expr = store_alloc(&store);
TEST_ASSERT_NOT_NULL(expr);
}
static void test_two_calls_to_alloc_return_distinct(void)
{
const expr_t *const a = store_alloc(&store);
const expr_t *const b = store_alloc(&store);
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();
}