Compare commits
10 Commits
0b4c586ae9
...
fade9395fa
| Author | SHA1 | Date | |
|---|---|---|---|
| fade9395fa | |||
| a03ef58eca | |||
| f97cea9290 | |||
| e50fd10f9b | |||
| f41d3a949c | |||
| 23bbbea755 | |||
| 00d1961d36 | |||
| 3e8a9d6789 | |||
| 657f0922bb | |||
| c8bacab6fe |
1
.clang-tidy
Normal file
1
.clang-tidy
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Checks: '-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling'
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
build/
|
||||||
25
CMakeLists.txt
Normal file
25
CMakeLists.txt
Normal 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
108
LICENSE.md
Normal 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
10
lib/CMakeLists.txt
Normal 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
22
lib/am.c
Normal 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
80
lib/env.c
Normal 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
17
lib/include/am.h
Normal 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
11
lib/include/env.h
Normal 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
43
lib/include/expr.h
Normal 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
|
||||||
15
lib/include/memory_stream.h
Normal file
15
lib/include/memory_stream.h
Normal 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
27
lib/include/parse.h
Normal 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
16
lib/include/store.h
Normal 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
20
lib/include/stream.h
Normal 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
26
lib/include/token.h
Normal 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
34
lib/memory_stream.c
Normal 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
119
lib/parse.c
Normal 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
14
lib/store.c
Normal 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
71
lib/token.c
Normal 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
4
scripts/build-and-test.sh
Executable 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
2
scripts/check-format.sh
Executable 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
3
scripts/lint.sh
Executable 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
6
scripts/list-source-files.sh
Executable 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
17
tests/CMakeLists.txt
Normal 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
30
tests/am_tests.c
Normal 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
78
tests/env_tests.c
Normal 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
229
tests/parse_tests.c
Normal 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
34
tests/store_tests.c
Normal 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
329
tests/token_tests.c
Normal 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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user