aboutsummaryrefslogtreecommitdiff
path: root/src/ls_internal.h
blob: 524f5b45480f5b7bc0ca0a051327e5c659d7cef8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// This software disclaims copyright. Do what you want with it. Be gay, do
// crime. Originally written by Alexis Lockwood in 2021. Ⓐ

#ifndef LS_INTERNAL_H
#define LS_INTERNAL_H

// --- DEPENDENCIES ------------------------------------------------------------

// Supporting modules
#include "ls.h"
#include "ls_types.h"
#include "ls_kws.h"

// Standard headers
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>

// --- PUBLIC MACROS -----------------------------------------------------------

#ifndef LS_PROGMEM
#define LS_PROGMEM
#endif

#define LS_HASHMAP_NULL_VALUE UINT8_MAX

/// Return whether an entry in littlescript_kw_hashmap_indices[] is null - that
/// is, does not point to a list of keywords.
#define LS_HASHMAP_INDEX_IS_NULL(x) ((x) == LS_HASHMAP_NULL_VALUE)

/// Return whether an entry in littlescript_kw_hashmap[] is the sentinel value.
#define LS_HASHMAP_ENTRY_IS_SENTINEL(x) !!((x) & 0x80u)

/// Get the original value from a littlescript_kw_hashmap[] sentinel.
#define LS_HASHMAP_ENTRY_UNPACK_SENTINEL(x) ((x) & 0x7Fu)

/// Define a keyword function. This is used by the code generated by gen_kws.py
#define LS_KW_FUN(n) \
	void ls_kw_fun_ ## n ( \
		ls_t * self \
	)

/// Initial hash value. Set a uint8_t to this and then use
/// LS_KW_HASH() to update with each character
#define LS_KW_HASH_DEFAULT 0

/// Update a hash value
///
/// The ternary arithmetic is a rough fast toupper(). It will hash symbols
/// above ascii 95 incorrectly, but those aren't allowed in keywords anyway.
//#define LS_KW_HASH(old, ch) ((old) + ((ch) >= 'a' ? (ch) - ('a' - 'A') : (ch)))
#define LS_KW_HASH(old, ch) (uint8_t)((old) + (uint8_t)(ch))

/// Finalize a hash value. This performs any final operations necessary to the
/// result of LS_KW_HASH() to produce the actual hash value. Call
/// once, after hashing the entire keyword.
#define LS_KW_HASH_FINALIZE(h) ((h) & 0x1F)

/// Bail if the expression is an error, returning that error
#define LS_TRY(e) \
	do { ls_error_t __err = (e); if (__err) return __err; } while (0)

// --- PRIVATE DATATYPES -------------------------------------------------------
// --- PUBLIC DATATYPES --------------------------------------------------------

typedef void (*ls_kw_fun_t)(ls_t * self);

typedef struct {
	char const LS_PROGMEM * name;
	ls_kw_fun_t fun;
} ls_kwdef_t;

// --- PUBLIC CONSTANTS --------------------------------------------------------

/// First level of the keyword hashmap. Once a candidate keyword has been
/// hashed, look it up in this lookup table (guaranteed to be long enough for
/// anything returned by LS_KW_HASH_FINALIZE()).
///
/// Check if it is a null entry with LS_HASHMAP_INDEX_IS_NULL(). If
/// this returns true, then there are no keywords with this hash value. If not,
/// move to the second level: littlescript_kw_hashmap[].
extern const LS_PROGMEM uint8_t ls_kw_hashmap_indices[];

/// Second level of the keyword hashmap. The list of keyword indices matching a
/// hash starts at the offset in this array returned from
/// ls_kw_hashmap_indices[hash], and continues until the first one for which
/// LS_HASHMAP_ENTRY_IS_SENTINEL() is true. The sentinel contains data too -
/// unpack that with LS_HASHMAP_ENTRY_UNPACK_SENTINEL().
///
/// Values in this array are the keyword indices - values of type
/// littlescript_kw_t and also indices into littlescript_kwmap[].
extern const LS_PROGMEM uint8_t ls_kw_hashmap[];

/// Table of definitions for keywords, indexed by the keyword number
/// (littlescript_kw_t). The value LS_COUNT_KWS indicates the length
/// of this array.
extern const LS_PROGMEM ls_kwdef_t ls_kwmap[];

// --- PUBLIC VARIABLES --------------------------------------------------------
// --- PUBLIC FUNCTIONS --------------------------------------------------------

/// Initialize an ls_t as if to run, but do not execute.
///
/// @param pool - pool to load (can be null, but then pool and callstack will
///  not get initialized)
void ls_init(ls_t * self, ls_value_t * pool, size_t szpool);

/// Identify what type the next token is, but do not consume it.
///
/// Can throw on internal errors, but returns LS_TOK_INVALID on a syntax error.
ls_token_ty_t ls_ident_token(ls_t * self, int offset);

/// Try to convert a word to a keyword. Note that this does not take
/// abbreviations. Those are converted simply by casting to ls_kw_t after
/// confirming valid range.
///
/// @return keyword
/// @retval LS_NOT_A_KW if the word does not match any keywords
ls_kw_t ls_convert_kw(char const * word);

/// Throw an error, to be caught by the setjmp in the interpreter. If the error
/// is LS_OK, no throw occurs.
void ls_throw_err(ls_t * self, ls_error_t e);

/// Fetch a character, relative to the current one. Throws if the fetcher
/// returns an error other than LS_NO_PROGRAM. If there is no character,
/// returns nul.
ls_uchar ls_fetch_rel(ls_t * self, int offset);

/// Fetch a character at a specific location. If there is no character, returns
/// nul.
ls_uchar ls_fetch(ls_t * self, ls_addr_t pc);

/// Allocate from the pool, throwing LS_OUT_OF_MEMORY if not possible. Always
/// returns a valid pointer (or doesn't return)
ls_value_t * ls_alloc(ls_t * self);

/// Deallocate, returning to the pool.
void ls_free(ls_t * self, ls_value_t * v);

/// Free a value, including anything it points to
void ls_free_val(ls_t * self, ls_value_t * v);

/// Count the number of free blocks
size_t ls_mem_avail(ls_t * self);

/// Find and return a variable by name.
///
/// @retval NULL if the variable does not exist
ls_value_t * ls_find_var(ls_t * self, char const * name);

/// Create a new variable in the current scope.
///
/// @return the variable
ls_value_t * ls_new_var(ls_t * self, char const * name);

/// Read an integer variable. If the variable does not contain an integer,
/// throw LS_TYPE_MISMATCH.
ls_int_t ls_read_int_var(ls_t * self, ls_value_t * var);

/// Write an integer variable. If the variable contains a different type, its
/// type will be changed.
void ls_write_int_var(ls_t * self, ls_value_t * var, ls_int_t val);

/// Write a variable of any type. val is not consumed and is assumed not
/// to live in the pool (space is allocated as needed).
void ls_write_var(ls_t * self, ls_value_t * var, ls_value_t * val);

/// Execute one line.
///
/// @return whether there was a line to execute. This will return false if
/// the script reaches the end, or if the END keyword or an external abort
/// occur.
bool ls_exec_line(ls_t * self);

#endif // !defined(LS_INTERNAL_H)