aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--compiler/compile.c419
-rw-r--r--docs/design/001-language-v0.md32
-rw-r--r--docs/design/002-dsl-direction.md4
-rw-r--r--tests/check.py162
5 files changed, 493 insertions, 126 deletions
diff --git a/README.md b/README.md
index 26d1fcd..882cf61 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ aup는 [Pure](/srv/git/pure.git) 런타임을 위한 **언어와 툴체인**이�
| [Vega](/srv/git/vega.git) | aup와 Pure 위의 인터페이스 구현자(TUI·CLI·GUI·NUI) |
상태: v0a(scalar 상태·키 입력·원자적 이벤트·보기), v0b(바이트·영속 문서·메시지·응답·text·커서),
-v0c(레코드·리스트·`index of`·조건식·표 보기·`fn`·`use` 라이브러리·집계·키 있는 리스트·text 비교·`bool`) 동작. 그 위의 첫 인터페이스는 [Vega](/srv/git/vega.git)(`use vega`, 목록·표·톤을 TUI·CLI·문장으로).
+v0c(레코드·리스트·`index of`·조건식·표 보기·`fn`·`use` 라이브러리·집계·키 있는 리스트·text 비교·`bool`·`i64`) 동작. 그 위의 첫 인터페이스는 [Vega](/srv/git/vega.git)(`use vega`, 목록·표·톤을 TUI·CLI·문장으로).
[언어 v0](docs/design/001-language-v0.md).
```sh
diff --git a/compiler/compile.c b/compiler/compile.c
index 6a690d5..0c71bc3 100644
--- a/compiler/compile.c
+++ b/compiler/compile.c
@@ -18,19 +18,20 @@ enum { CONST, LOAD, DT, ADD, SUB, MUL, DIV, NEG, LT, LE, GT, GE, EQ, NE, AND, OR
TEXT_SET, TEXT_FROM, BYTES_FROM, CUR_COUNT, LIST_COUNT, LIST_APPEND, LIST_INSERT, LIST_REMOVE,
ITEM_GET, ITEM_SET, INDEX_LOAD, INDEX_STORE, INDEX_MOVE, INDEX_FIRST, INDEX_LAST, INDEX_NUM, ROW,
PICK, SLIDE, FOLD, TEXT_COMPARE, LIST_FIND, LIST_HAS, NOW, FLOOR, MOD, SEND, OUTER_ROW, EACH,
- LOCAL_LOAD, LOCAL_STORE };
+ LOCAL_LOAD, LOCAL_STORE, I64_ADD, I64_SUB, I64_MUL, I64_DIV, I64_MOD, I64_NEG, I64_COMPARE, I64_MIN, I64_MAX,
+ I64_FROM, NUM_FROM };
enum { LOCALS = 64 }; /* an event's locals (Pure's) */
-enum { FOLD_COUNT, FOLD_SUM, FOLD_MAX, FOLD_MIN, FOLD_FIRST, FOLD_DISTINCT };
+enum { FOLD_COUNT, FOLD_SUM, FOLD_MAX, FOLD_MIN, FOLD_FIRST, FOLD_DISTINCT, FOLD_INTEGER = 0x40 };
enum { KIND_KEY = 1, KIND_MESSAGE = 2, KIND_TEXT = 3, KIND_TIMER = 4, KIND_OUTPUT = 5, KIND_ARGUMENTS = 6,
FIELD_U32 = 1, FIELD_BYTES = 2, FIELD_NUMBER = 3, FIELD_TEXT = 4,
- FIELD_ROWS = 5, FIELD_BOOL = 6 };
+ FIELD_ROWS = 5, FIELD_BOOL = 6, FIELD_I64 = 7 };
/* A list of a message's rows: filled as the message arrives, emptied when its event ends (Pure's flag). */
enum { LIST_ROWS = 256 };
-enum { STATE_NUM = 0, STATE_TEXT = 1, STATE_CURSOR = 2, STATE_LIST = 3, STATE_INDEX = 4, STATE_BOOL = 5,
+enum { STATE_NUM = 0, STATE_TEXT = 1, STATE_CURSOR = 2, STATE_LIST = 3, STATE_INDEX = 4, STATE_BOOL = 5, STATE_I64 = 6,
UNIT_CHAR = 0, UNIT_LINE = 1 };
-/* Pure's view types; a bool record field is type 2 and a bool local holds 2. */
-enum { VIEW_NUM = 0, VIEW_TEXT = 1, VIEW_TABLE = 2, VIEW_BOOL = 3, FIELD_RECORD_BOOL = 2, LOCAL_BOOL = 2 };
-typedef enum { NUM = 1, BYTES, TEXT, CURSOR, UNIT, INDEX, BOOL } Type;
+/* Pure's view types, record field types (0 a number, 1 a text, 2 a bool, 3 an i64) and what a local holds. */
+enum { VIEW_NUM = 0, VIEW_TEXT = 1, VIEW_TABLE = 2, VIEW_BOOL = 3, VIEW_I64 = 4, LOCAL_BOOL = 2, LOCAL_I64 = 3 };
+typedef enum { NUM = 1, BYTES, TEXT, CURSOR, UNIT, INDEX, BOOL, I64 } Type;
typedef struct { Type type; int owner; } Kind; /* owner: a cursor's text state or an index's list state */
typedef enum { T_EOF, T_NL, T_NUMBER, T_NAME, T_PUNCT, T_STRING } TokenKind;
@@ -38,6 +39,8 @@ typedef struct {
TokenKind kind;
int line, column;
double number;
+ int64_t integer; /* a literal of digits alone (whole): its exact value, when not past an i64's range (wide) */
+ bool whole, wide;
char text[NAME + 1]; /* name, or punctuation of one or two characters */
size_t start, end; /* string contents in the source */
} Token;
@@ -63,6 +66,8 @@ typedef struct Expr {
ExprKind kind;
int line, column, op;
double number;
+ int64_t integer; /* E_NUMBER: as its token's */
+ bool whole, wide;
char name[NAME], member[NAME];
struct Expr *args[PARAMS], *next;
unsigned count;
@@ -97,13 +102,14 @@ typedef struct {
unsigned view; /* arguments: the view they are for */
} Input;
-/* A record: fields in order, each a number, a bool or text with its default. */
+/* A record: fields in order, each a number, a bool, an i64 or text with its default. */
typedef struct {
char name[NAME];
int line, column;
char field_names[FIELDS_PER_RECORD][NAME];
- bool field_text[FIELDS_PER_RECORD], field_bool[FIELDS_PER_RECORD];
+ Type field_kinds[FIELDS_PER_RECORD]; /* NUM, BOOL, I64 or TEXT */
double field_number[FIELDS_PER_RECORD];
+ int64_t field_integer[FIELDS_PER_RECORD];
Expr *field_string[FIELDS_PER_RECORD]; /* a text default, or NULL for "" */
uint32_t field_constant[FIELDS_PER_RECORD];
unsigned fields;
@@ -144,6 +150,7 @@ typedef struct {
char state_key[STATES][NAME];
int state_key_line[STATES], state_key_column[STATES], list_key[STATES];
double initial[STATES];
+ int64_t state_integer[STATES]; /* an i64 state's start */
Expr *state_text[STATES];
unsigned states;
char collection_names[COLLECTIONS][NAME];
@@ -191,7 +198,8 @@ typedef struct {
unsigned fn_count;
Scope *scope; /* the fn being inlined, or NULL */
unsigned joins; /* branches ahead that carry a value */
- struct { uint8_t op; uint32_t arg; double value; } code[CODE];
+ Type want; /* what the expression value() compiles next is wanted as: an i64 makes a literal one */
+ struct { uint8_t op; uint32_t arg; union { double value; int64_t integer; }; } code[CODE];
unsigned code_count;
uint32_t view_code[VIEWS][2], handler_code[HANDLERS][2];
int view_types[VIEWS];
@@ -299,6 +307,15 @@ static Token scan(Compiler *c) {
token.kind = T_NUMBER;
token.number = strtod(text, NULL);
if (!isfinite(token.number)) fail_at(c, token.line, token.column, "number must be finite");
+ /* Digits alone: exactly, for an i64 (past its range, wide). */
+ token.whole = strspn(text, "0123456789") == n;
+ uint64_t exact = 0;
+ for (size_t i = 0; token.whole && i < n && !token.wide; ++i) {
+ unsigned digit = (unsigned)(text[i] - '0');
+ token.wide = exact > (UINT64_C(9223372036854775807) - digit) / 10;
+ exact = exact * 10 + digit;
+ }
+ token.integer = token.wide ? 0 : (int64_t)exact;
return token;
}
if (letter(ch)) {
@@ -695,6 +712,9 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) {
if (token.kind == T_NUMBER) {
left = node(c, E_NUMBER, token.line, token.column);
left->number = token.number;
+ left->integer = token.integer;
+ left->whole = token.whole;
+ left->wide = token.wide;
} else if (token.kind == T_STRING) {
left = string(c, &token);
} else if (token.kind == T_PUNCT && (!strcmp(token.text, "-") || !strcmp(token.text, "!"))) {
@@ -702,6 +722,13 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) {
left->op = token.text[0] == '-' ? NEG : NOT;
left->args[0] = expression(c, 7, multiline);
left->count = 1;
+ if (left->op == NEG && left->args[0]->kind == E_NUMBER) { /* -5: one literal, a number or an i64 */
+ Expr *literal = left->args[0];
+ literal->number = -literal->number;
+ literal->integer = -literal->integer;
+ literal->line = left->line, literal->column = left->column;
+ left = literal;
+ }
} else if (token.kind == T_PUNCT && !strcmp(token.text, "(")) {
left = expression(c, 0, true);
newlines(c);
@@ -712,7 +739,7 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) {
} else if (token.kind == T_NAME) {
static const struct { const char *name; unsigned count; int op; } calls[] =
{{"min", 2, MIN}, {"max", 2, MAX}, {"clamp", 3, CLAMP}, {"text", 1, TEXT_FROM}, {"bytes", 1, BYTES_FROM},
- {"floor", 1, FLOOR}};
+ {"floor", 1, FLOOR}, {"i64", 1, I64_FROM}, {"number", 1, NUM_FROM}};
int call = -1, how = -1;
for (int i = 0; i < (int)(sizeof(calls) / sizeof(*calls)); ++i) if (!strcmp(token.text, calls[i].name)) call = i;
static const char *const folds[] = {"count", "sum", "max", "min", "first", "distinct"};
@@ -1097,7 +1124,7 @@ static int used_library(const Compiler *c, int module, const char *name) {
}
static const char *declared_kind(const Compiler *c, const char *name) {
- static const char *const states[] = {"a state", "a text state", "a cursor", "a list", "a list index", "a state"};
+ static const char *const states[] = {"a state", "a text state", "a cursor", "a list", "a list index", "a state", "a state"};
if (fn_index(c, c->module, name) >= 0) return "a fn";
if (used_library(c, c->module, name) >= 0) return "a library";
if (c->module) return NULL; /* a library has only fns and uses */
@@ -1188,9 +1215,10 @@ static void message_fields(Compiler *c, Input *in, const char *group) {
continue;
}
if (c->field_count == FIELDS) fail_at(c, name.line, name.column, "at most %d message fields are allowed", FIELDS);
- Token type = expect_name(c, "u32, number, bool, bytes, text or list of a record");
+ Token type = expect_name(c, "u32, i64, number, bool, bytes, text or list of a record");
static const struct { const char *name; int type; } types[] = {
- {"u32", FIELD_U32}, {"number", FIELD_NUMBER}, {"bool", FIELD_BOOL}, {"bytes", FIELD_BYTES}, {"text", FIELD_TEXT}};
+ {"u32", FIELD_U32}, {"i64", FIELD_I64}, {"number", FIELD_NUMBER}, {"bool", FIELD_BOOL}, {"bytes", FIELD_BYTES},
+ {"text", FIELD_TEXT}};
int field_type = 0;
for (size_t t = 0; t < sizeof(types) / sizeof(*types); ++t)
if (!strcmp(type.text, types[t].name)) field_type = types[t].type;
@@ -1204,7 +1232,7 @@ static void message_fields(Compiler *c, Input *in, const char *group) {
in->field_column[in->fields] = record.column;
field_type = FIELD_ROWS;
}
- if (!field_type) fail_at(c, type.line, type.column, "field types are u32, number, bool, bytes, text and list of a record");
+ if (!field_type) fail_at(c, type.line, type.column, "field types are u32, i64, number, bool, bytes, text and list of a record");
memcpy(in->field_names[in->fields], path, NAME);
in->field_types[in->fields++] = field_type;
++c->field_count;
@@ -1223,6 +1251,17 @@ static double signed_number(Compiler *c, const char *what) {
return sign * take(c).number;
}
+/* A signed integer literal in an i64's range: an i64 state's or field's start. */
+static int64_t signed_integer(Compiler *c, const char *what) {
+ bool negative = false;
+ if (is(c, "-") || is(c, "+")) negative = take(c).text[0] == '-';
+ if (c->token.kind != T_NUMBER || !c->token.whole)
+ fail_at(c, c->token.line, c->token.column, "expected a whole number for %s", what);
+ if (c->token.wide) fail_at(c, c->token.line, c->token.column, "the number is past an i64's range");
+ int64_t value = take(c).integer;
+ return negative ? -value : value;
+}
+
/* A plain string with no {expressions}: a text state or field default. */
static Expr *plain_string(Compiler *c, const char *what) {
if (c->token.kind != T_STRING) fail_at(c, c->token.line, c->token.column, "%s starts from a string", what);
@@ -1259,18 +1298,21 @@ static void record_declaration(Compiler *c) {
++c->record_fields;
if (is(c, ":")) {
take(c);
- Token type = expect_name(c, "text");
- if (strcmp(type.text, "text"))
- fail_at(c, type.line, type.column, "a field is a number (field = 0), a bool (field = false) or text (field: text)");
- r->field_text[index] = true;
+ Token type = expect_name(c, "text or i64");
+ if (strcmp(type.text, "text") && strcmp(type.text, "i64"))
+ fail_at(c, type.line, type.column,
+ "a field is a number (field = 0), a bool (field = false), text (field: text) or an i64 (field: i64)");
+ r->field_kinds[index] = type.text[0] == 't' ? TEXT : I64;
if (is(c, "=")) {
take(c);
- r->field_string[index] = plain_string(c, "a text field");
+ if (r->field_kinds[index] == TEXT) r->field_string[index] = plain_string(c, "a text field");
+ else r->field_integer[index] = signed_integer(c, "an i64 field's default");
}
} else {
- expect(c, "=", "'=' and a number or a bool, or ': text'");
+ expect(c, "=", "'=' and a number or a bool, or ': text' or ': i64'");
+ r->field_kinds[index] = NUM;
if (word(c, "true") || word(c, "false")) {
- r->field_bool[index] = true;
+ r->field_kinds[index] = BOOL;
r->field_number[index] = take(c).text[0] == 't';
} else r->field_number[index] = signed_number(c, "field default");
}
@@ -1300,8 +1342,14 @@ static void state_declaration(Compiler *c) {
c->state_column[index] = name.column;
if (is(c, ":")) {
take(c);
- Token type = expect_name(c, "text, cursor, mark, list or index");
- if (!strcmp(type.text, "text")) {
+ Token type = expect_name(c, "text, i64, cursor, mark, list or index");
+ if (!strcmp(type.text, "i64")) { /* state n: i64 = 0: exact, whole */
+ c->state_types[index] = STATE_I64;
+ if (is(c, "=")) {
+ take(c);
+ c->state_integer[index] = signed_integer(c, "an i64 state");
+ }
+ } else if (!strcmp(type.text, "text")) {
c->state_types[index] = STATE_TEXT;
if (is(c, "=")) {
take(c);
@@ -1327,8 +1375,8 @@ static void state_declaration(Compiler *c) {
c->state_key_column[index] = key.column;
}
} else fail_at(c, type.line, type.column,
- "state types are numbers, text, cursor or mark in a text, list of a record, index of a list, "
- "and bytes by key");
+ "state types are numbers, bools, i64, text, cursor or mark in a text, list of a record, index of "
+ "a list, and bytes by key");
++c->states;
return;
}
@@ -1499,13 +1547,14 @@ static void view_arguments(Compiler *c, unsigned view, const Token *name) {
if (!strcmp(in->field_names[f], argument.text))
fail_at(c, argument.line, argument.column, "duplicate argument '%s'", argument.text);
expect(c, ":", "':'");
- Token type = expect_name(c, "u32, number, bool, bytes or text");
+ Token type = expect_name(c, "u32, i64, number, bool, bytes or text");
static const struct { const char *name; int type; } types[] = {
- {"u32", FIELD_U32}, {"number", FIELD_NUMBER}, {"bool", FIELD_BOOL}, {"bytes", FIELD_BYTES}, {"text", FIELD_TEXT}};
+ {"u32", FIELD_U32}, {"i64", FIELD_I64}, {"number", FIELD_NUMBER}, {"bool", FIELD_BOOL}, {"bytes", FIELD_BYTES},
+ {"text", FIELD_TEXT}};
int field_type = 0;
for (size_t t = 0; t < sizeof(types) / sizeof(*types); ++t)
if (!strcmp(type.text, types[t].name)) field_type = types[t].type;
- if (!field_type) fail_at(c, type.line, type.column, "an argument is a u32, number, bool, bytes or text");
+ if (!field_type) fail_at(c, type.line, type.column, "an argument is a u32, i64, number, bool, bytes or text");
if (c->field_count == FIELDS) fail_at(c, argument.line, argument.column, "at most %d message fields are allowed", FIELDS);
memcpy(in->field_names[in->fields], argument.text, NAME);
in->field_line[in->fields] = argument.line;
@@ -1644,8 +1693,14 @@ static void emit(Compiler *c, int line, int column, uint8_t op, uint32_t arg, do
c->code[c->code_count++].value = value;
}
+/* An i64 constant: CONST with argument 2, its value the integer's 8 bytes. */
+static void emit_integer(Compiler *c, int line, int column, int64_t value) {
+ emit(c, line, column, CONST, 2, 0);
+ c->code[c->code_count - 1].integer = value;
+}
+
static const char *type_name(Type type) {
- static const char *const names[] = {"", "a number", "bytes", "text", "a cursor", "a distance", "an index", "a bool"};
+ static const char *const names[] = {"", "a number", "bytes", "text", "a cursor", "a distance", "an index", "a bool", "an i64"};
return names[type];
}
@@ -1715,12 +1770,12 @@ static unsigned record_field(Compiler *c, const Record *r, const char *field, in
/* A record field's kind: text, a bool or a number. */
static Type field_kind(const Record *r, unsigned field) {
- return r->field_text[field] ? TEXT : r->field_bool[field] ? BOOL : NUM;
+ return r->field_kinds[field];
}
/* The kind of a message field's value (a u32 is a number). */
static Type message_kind(int type) {
- return type == FIELD_TEXT ? TEXT : type == FIELD_BYTES ? BYTES : type == FIELD_BOOL ? BOOL : NUM;
+ return type == FIELD_TEXT ? TEXT : type == FIELD_BYTES ? BYTES : type == FIELD_BOOL ? BOOL : type == FIELD_I64 ? I64 : NUM;
}
static Kind value(Compiler *c, const Expr *e, bool handler, int height);
@@ -1743,6 +1798,111 @@ static void truth(Compiler *c, const Expr *e, bool handler, int height, const ch
expect_kind(c, e, kind, BOOL);
}
+/* An integer literal (digits alone, or negated ones): a number, or an i64
+ * where one is wanted. */
+static bool loose(const Expr *e) { return e->kind == E_NUMBER && e->whole; }
+
+/* The literal e, emitted at code[at] as a number, made the i64 wanted beside it. */
+static void made_i64(Compiler *c, const Expr *e, unsigned at) {
+ if (e->wide) fail_at(c, e->line, e->column, "the number is past an i64's range");
+ c->code[at].arg = 2;
+ c->code[at].integer = e->integer;
+}
+
+/* e where a value of type want goes (0: any): an integer literal is an i64
+ * where an i64 is wanted, and so are the literals of an arithmetic or a
+ * conditional expression that gives one. */
+static Kind wanted(Compiler *c, const Expr *e, bool handler, int height, Type want) {
+ if (want == I64 && loose(e)) {
+ if (height + 1 > STACK) fail_at(c, e->line, e->column, "expression needs more than 64 stack values");
+ emit(c, e->line, e->column, CONST, 2, 0);
+ made_i64(c, e, c->code_count - 1);
+ return (Kind){I64, -1};
+ }
+ c->want = want;
+ return value(c, e, handler, height);
+}
+
+/* e where an i64 goes: a number is refused, and the message says how to convert it. */
+static void integer(Compiler *c, const Expr *e, bool handler, int height) {
+ Kind kind = wanted(c, e, handler, height, I64);
+ if (kind.type == NUM)
+ fail_at(c, e->line, e->column, e->kind == E_NUMBER ? "an i64 is a whole number; this one is not" :
+ "expected an i64, not a number: convert it, as in i64(x)");
+ expect_kind(c, e, kind, I64);
+}
+
+/* Integer literals alone, with arithmetic, negation, min, max and
+ * conditional expressions of them: a number, or an i64 where one is wanted. */
+static bool literal_only(const Expr *e) {
+ if (loose(e)) return true;
+ if (e->kind == E_IF) return literal_only(e->args[1]) && literal_only(e->args[2]); /* whatever its condition */
+ bool arithmetic = (e->kind == E_BINARY && (e->op == ADD || e->op == SUB || e->op == MUL || e->op == DIV || e->op == MOD) &&
+ e->args[1]->kind != E_UNIT) ||
+ (e->kind == E_CALL && (e->op == MIN || e->op == MAX)) || (e->kind == E_UNARY && e->op == NEG);
+ for (unsigned i = 0; arithmetic && i < e->count; ++i) arithmetic = literal_only(e->args[i]);
+ return arithmetic;
+}
+
+/* The type e has, compiled and taken back: what literals beside it take. */
+static Type probe(Compiler *c, const Expr *e, bool handler, int height) {
+ unsigned code = c->code_count, constants = c->constants, joins = c->joins;
+ size_t used = c->constant_used;
+ Type type = value(c, e, handler, height).type;
+ c->code_count = code, c->constants = constants, c->constant_used = used, c->joins = joins;
+ return type;
+}
+
+/* What an arithmetic operation's or a comparison's operands are wanted as,
+ * before the left one is emitted: a number or an i64 when that is wanted of
+ * the operation (want), else when the left one is literals alone the right
+ * one's type (probed once: the right one is then compiled as wanted, so no
+ * probe below it runs again). */
+static Type left_hint(Compiler *c, const Expr *e, bool handler, int height, Type want) {
+ if (want == I64 || want == NUM) return want;
+ if (!literal_only(e->args[0]) || literal_only(e->args[1])) return 0;
+ Type right = probe(c, e->args[1], handler, height + 1);
+ return right == I64 || right == NUM ? right : 0;
+}
+
+/* The right operand of an arithmetic operation or a comparison whose left
+ * one, of kind left, was emitted: both numbers or both i64s. Their type. */
+static Type second(Compiler *c, const Expr *e, Kind left, bool handler, int height) {
+ const Expr *a = e->args[0], *b = e->args[1];
+ if (left.type == I64) {
+ integer(c, b, handler, height + 1);
+ return I64;
+ }
+ expect_kind(c, a, left, NUM);
+ Kind right = wanted(c, b, handler, height + 1, NUM);
+ if (right.type == I64)
+ fail_at(c, b->line, b->column, "an i64 and a number do not mix: convert one, as in i64(x) or number(n)");
+ expect_kind(c, b, right, NUM);
+ return NUM;
+}
+
+static uint8_t i64_op(int op) {
+ switch (op) {
+ case ADD: return I64_ADD;
+ case SUB: return I64_SUB;
+ case MUL: return I64_MUL;
+ case DIV: return I64_DIV;
+ case MOD: return I64_MOD;
+ case MIN: return I64_MIN;
+ default: return I64_MAX;
+ }
+}
+
+/* A conditional expression's branch, of the other branches' type (kind, once the first gives it). */
+static void if_branch(Compiler *c, const Expr *e, bool handler, int height, Type hint, Kind *kind) {
+ Kind got = wanted(c, e, handler, height, hint);
+ if (kind->type && (got.type != kind->type || got.owner != kind->owner))
+ fail_at(c, e->line, e->column, got.type != kind->type ? "the branches of if give %s and %s" : kind->type == CURSOR ?
+ "the branches of if give cursors of different texts" : "the branches of if give indices of different lists",
+ type_name(kind->type), type_name(got.type));
+ *kind = got;
+}
+
/* A cursor of the named text state. */
static void cursor_of(Compiler *c, const Expr *e, bool handler, int height, int text, const char *name) {
Kind kind = value(c, e, handler, height);
@@ -1763,11 +1923,16 @@ static void index_of(Compiler *c, const Expr *e, bool handler, int height, int l
/* The item name[e] names: e is an index of the list, or for a keyed list a
* key, found by LIST_FIND. */
static void item_of(Compiler *c, const Expr *e, bool handler, int height, int list) {
+ unsigned at = c->code_count;
Kind kind = value(c, e, handler, height);
int key = c->list_key[list] - 1;
if (kind.type != INDEX && key >= 0) {
const Record *r = record_of(c, list);
Type type = field_kind(r, (unsigned)key);
+ if (type == I64 && kind.type == NUM && loose(e)) { /* xs[5] by an i64 key */
+ made_i64(c, e, at);
+ kind.type = I64;
+ }
if (kind.type != type)
fail_at(c, e->line, e->column, "'%s' is keyed by %s, %s: expected %s or an index, not %s", c->state_names[list],
r->field_names[key], type_name(type), type_name(type), type_name(kind.type));
@@ -1878,11 +2043,13 @@ static int local_named(const Compiler *c, const char *name) {
static uint32_t local_arg(const Compiler *c, int local) {
const Kind *kind = &c->locals[local].kind;
return c->locals[local].slot | (kind->type == INDEX ? 1u << 8 | (uint32_t)kind->owner << 16 :
- kind->type == BOOL ? (uint32_t)LOCAL_BOOL << 8 : 0);
+ kind->type == BOOL ? (uint32_t)LOCAL_BOOL << 8 : kind->type == I64 ? (uint32_t)LOCAL_I64 << 8 : 0);
}
/* Emit an expression; height is the stack height before it. */
static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
+ Type want = c->want; /* set by wanted() for this expression alone */
+ c->want = 0;
if (height + 1 > STACK) fail_at(c, e->line, e->column, "expression needs more than 64 stack values");
switch (e->kind) {
case E_NUMBER: emit(c, e->line, e->column, CONST, 0, e->number); return (Kind){NUM, -1};
@@ -1901,9 +2068,10 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
int pieces = 0;
for (const Expr *part = e->args[0]; part; part = part->next) {
Kind kind = value(c, part, handler, height + (pieces > 0));
- if (kind.type == NUM || kind.type == BOOL) emit(c, part->line, part->column, NUM_TEXT, kind.type == BOOL, 0);
+ if (kind.type == NUM || kind.type == BOOL || kind.type == I64)
+ emit(c, part->line, part->column, NUM_TEXT, kind.type == BOOL ? 1 : kind.type == I64 ? 2 : 0, 0);
else if (kind.type != TEXT)
- fail_at(c, part->line, part->column, "a string shows numbers, bools and text, not %s", type_name(kind.type));
+ fail_at(c, part->line, part->column, "a string shows numbers, bools, i64s and text, not %s", type_name(kind.type));
if (pieces++) emit(c, part->line, part->column, CONCAT, 0, 0);
}
return (Kind){TEXT, -1};
@@ -1954,8 +2122,8 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
if (type == STATE_LIST)
fail_at(c, e->line, e->column, "'%s' is a list; read %s.count or a field, as in %s[index].field",
e->name, e->name, e->name);
- static const uint8_t loads[] = {LOAD, TEXT_LOAD, CUR_LOAD, 0, INDEX_LOAD, LOAD};
- static const Type types[] = {NUM, TEXT, CURSOR, 0, INDEX, BOOL};
+ static const uint8_t loads[] = {LOAD, TEXT_LOAD, CUR_LOAD, 0, INDEX_LOAD, LOAD, LOAD};
+ static const Type types[] = {NUM, TEXT, CURSOR, 0, INDEX, BOOL, I64};
emit(c, e->line, e->column, loads[type], (uint32_t)index, 0);
return (Kind){types[type], type == STATE_CURSOR || type == STATE_INDEX ? cursor_owner(c, index) : -1};
}
@@ -2058,7 +2226,7 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
e->member, record_of(c, list)->name);
if (e->count != 1) fail_at(c, e->line, e->column, "%s.has takes one key", e->member);
Type type = field_kind(record_of(c, list), (unsigned)c->list_key[list] - 1);
- Kind given = value(c, e->args[0], handler, height);
+ Kind given = wanted(c, e->args[0], handler, height, type);
if (given.type != type)
fail_at(c, e->args[0]->line, e->args[0]->column, "'%s' is keyed by %s, %s: expected %s, not %s", e->member, key,
type_name(type), type_name(type), type_name(given.type));
@@ -2112,21 +2280,24 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
}
case E_IF: {
/* Arms of an else-if chain share one end; a value crosses it, so the
- * branches ahead that carry one are counted against Pure's limit. */
+ * branches ahead that carry one are counted against Pure's limit. An
+ * integer literal branch takes the other branches' type. */
unsigned ends[DEPTH], arms = 0, saved = c->joins;
Kind kind = {0, -1};
+ /* Literal branches take the type of the first that is no literal (an i64's, then). */
+ Type hint = want == I64 ? I64 : 0;
+ for (const Expr *at = e; !hint; at = at->args[2]) {
+ if (!literal_only(at->args[1])) hint = probe(c, at->args[1], handler, height) == I64 ? I64 : NUM;
+ else if (at->args[2]->kind != E_IF) hint = literal_only(at->args[2]) ? NUM : probe(c, at->args[2], handler, height) == I64 ? I64 : NUM;
+ else continue;
+ }
const Expr *arm = e;
for (;;) {
truth(c, arm->args[0], handler, height, "if");
unsigned jump = c->code_count;
emit(c, arm->line, arm->column, JZ, 0, 0);
if (height && ++c->joins > JOINS) fail_at(c, arm->line, arm->column, "conditional expressions nest too deeply");
- Kind then = value(c, arm->args[1], handler, height);
- if (arms && (then.type != kind.type || then.owner != kind.owner))
- fail_at(c, arm->args[1]->line, arm->args[1]->column, then.type != kind.type ?
- "the branches of if give %s and %s" : "the branches of if point into different texts or lists",
- type_name(kind.type), type_name(then.type));
- kind = then;
+ if_branch(c, arm->args[1], handler, height, hint, &kind);
ends[arms++] = c->code_count;
emit(c, arm->line, arm->column, JMP, 0, 0);
if (arms == 1 && ++c->joins > JOINS) fail_at(c, arm->line, arm->column, "conditional expressions nest too deeply");
@@ -2135,12 +2306,7 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
if (arm->args[2]->kind != E_IF) break;
arm = arm->args[2];
}
- Kind otherwise = value(c, arm->args[2], handler, height);
- if (otherwise.type != kind.type || otherwise.owner != kind.owner)
- fail_at(c, arm->args[2]->line, arm->args[2]->column, otherwise.type != kind.type ?
- "the branches of if give %s and %s" : kind.type == CURSOR ?
- "the branches of if give cursors of different texts" : "the branches of if give indices of different lists",
- type_name(kind.type), type_name(otherwise.type));
+ if_branch(c, arm->args[2], handler, height, hint, &kind);
for (unsigned i = 0; i < arms; ++i) c->code[ends[i]].arg = c->code_count;
c->joins = saved;
return kind;
@@ -2156,10 +2322,11 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
if (c->filtering) fail_at(c, e->line, e->column, "a table's where cannot hold a count, sum, max or min");
bool sliced = e->args[2] != NULL;
if (sliced) { /* the keys from low up to before high, found by key */
- if (!keyed_by(c, list) || field_kind(record_of(c, list), (unsigned)c->list_key[list] - 1) != NUM)
- fail_at(c, e->line, e->column, "'%s' is not keyed by a number: a slice takes one", e->member);
- number(c, e->args[2], handler, height);
- number(c, e->args[3], handler, height + 1);
+ Type key = keyed_by(c, list) ? field_kind(record_of(c, list), (unsigned)c->list_key[list] - 1) : TEXT;
+ if (key != NUM && key != I64)
+ fail_at(c, e->line, e->column, "'%s' is not keyed by a number or an i64: a slice takes one", e->member);
+ expect_kind(c, e->args[2], wanted(c, e->args[2], handler, height, key), key);
+ expect_kind(c, e->args[3], wanted(c, e->args[3], handler, height + 1, key), key);
}
unsigned at = c->code_count, joins = c->joins;
emit(c, e->line, e->column, FOLD, 0, 0);
@@ -2169,19 +2336,23 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
memcpy(c->fold_name, e->name, NAME);
c->joins = 0;
bool itemwise = e->op == FOLD_COUNT || e->op == FOLD_FIRST;
+ Type values = NUM;
if (!itemwise) {
Kind kind = value(c, e->args[1], false, 0);
- if (kind.type != NUM)
- fail_at(c, e->args[1]->line, e->args[1]->column, "sum, max, min and distinct take numbers, not %s",
+ if (kind.type != NUM && kind.type != I64)
+ fail_at(c, e->args[1]->line, e->args[1]->column, "sum, max, min and distinct take numbers or i64s, not %s",
type_name(kind.type));
+ values = kind.type;
}
if (e->args[0]) truth(c, e->args[0], false, !itemwise, "where");
else emit(c, e->line, e->column, CONST, 1, 1); /* every item */
c->folding = false;
c->fold_list = -1;
c->joins = joins;
- c->code[at].arg = (uint32_t)e->op | (sliced ? 0x80u : 0) | (uint32_t)list << 8 | (uint32_t)(c->code_count - at - 1) << 16;
- return e->op == FOLD_FIRST ? (Kind){INDEX, list} : (Kind){NUM, -1};
+ c->code[at].arg = (uint32_t)e->op | (values == I64 ? FOLD_INTEGER : 0) | (sliced ? 0x80u : 0) | (uint32_t)list << 8 |
+ (uint32_t)(c->code_count - at - 1) << 16;
+ return e->op == FOLD_FIRST ? (Kind){INDEX, list} :
+ (Kind){values == I64 && e->op != FOLD_DISTINCT ? I64 : NUM, -1}; /* an i64's sum, most or least */
}
case E_RECORD: case E_FIELD:
fail_at(c, e->line, e->column, "a record value goes into a list, as in list.append(%s { ... })", e->name);
@@ -2191,20 +2362,23 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
if (e->op == ADD || e->op == SUB) {
/* at + 1 char moves a cursor, at + 1 an index; both stop at the ends. */
bool distance = e->args[1]->kind == E_UNIT;
- Kind left = value(c, e->args[0], handler, height);
+ Kind left = wanted(c, e->args[0], handler, height, distance ? 0 : left_hint(c, e, handler, height, want));
if (distance && left.type != CURSOR)
fail_at(c, e->line, e->column, "only a cursor moves by a distance, not %s", type_name(left.type));
- if (!distance && left.type != INDEX) expect_kind(c, e->args[0], left, NUM);
- number(c, distance ? e->args[1]->args[0] : e->args[1], handler, height + 1);
- if (left.type == NUM) { emit(c, e->line, e->column, (uint8_t)e->op, 0, 0); return left; }
- if (e->op == SUB) emit(c, e->line, e->column, NEG, 0, 0);
- if (distance) emit(c, e->line, e->column, CUR_MOVE, (uint32_t)e->args[1]->op, 0);
- else emit(c, e->line, e->column, INDEX_MOVE, 0, 0);
- return left;
+ if (distance || left.type == INDEX) {
+ number(c, distance ? e->args[1]->args[0] : e->args[1], handler, height + 1);
+ if (e->op == SUB) emit(c, e->line, e->column, NEG, 0, 0);
+ if (distance) emit(c, e->line, e->column, CUR_MOVE, (uint32_t)e->args[1]->op, 0);
+ else emit(c, e->line, e->column, INDEX_MOVE, 0, 0);
+ return left;
+ }
+ Type type = second(c, e, left, handler, height);
+ emit(c, e->line, e->column, type == I64 ? i64_op(e->op) : (uint8_t)e->op, 0, 0);
+ return (Kind){type, -1};
}
if (e->op >= LT && e->op <= NE) {
- /* Texts compare by their bytes, numbers as numbers; bools are equal or not. */
- Kind left = value(c, e->args[0], handler, height);
+ /* Texts compare by their bytes, numbers and i64s as numbers; bools are equal or not. */
+ Kind left = wanted(c, e->args[0], handler, height, left_hint(c, e, handler, height, 0));
if (left.type == TEXT) {
expect_kind(c, e->args[1], value(c, e->args[1], handler, height + 1), TEXT);
emit(c, e->line, e->column, TEXT_COMPARE, (uint32_t)(e->op - LT), 0);
@@ -2216,21 +2390,54 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
emit(c, e->line, e->column, (uint8_t)e->op, 0, 0);
return (Kind){BOOL, -1};
}
- expect_kind(c, e->args[0], left, NUM);
- number(c, e->args[1], handler, height + 1);
- emit(c, e->line, e->column, (uint8_t)e->op, 0, 0);
+ if (second(c, e, left, handler, height) == I64) emit(c, e->line, e->column, I64_COMPARE, (uint32_t)(e->op - LT), 0);
+ else emit(c, e->line, e->column, (uint8_t)e->op, 0, 0);
return (Kind){BOOL, -1};
}
/* fall through */
default: {
- /* !, && and || take bools (both sides are computed); the rest numbers. */
+ /* !, && and || take bools (both sides are computed); *, /, %, min and
+ * max numbers or i64s; clamp and floor numbers. */
bool logic = e->op == AND || e->op == OR || e->op == NOT;
- for (unsigned i = 0; i < e->count; ++i) {
- if (logic) truth(c, e->args[i], handler, height + (int)i, e->op == NOT ? "!" : e->op == AND ? "&&" : "||");
- else number(c, e->args[i], handler, height + (int)i);
+ if (logic) {
+ for (unsigned i = 0; i < e->count; ++i)
+ truth(c, e->args[i], handler, height + (int)i, e->op == NOT ? "!" : e->op == AND ? "&&" : "||");
+ emit(c, e->line, e->column, (uint8_t)e->op, 0, 0);
+ return (Kind){BOOL, -1};
+ }
+ if (e->op == I64_FROM) { /* i64(x): a whole number in range; a literal is one exactly */
+ if (loose(e->args[0])) integer(c, e->args[0], handler, height);
+ else {
+ number(c, e->args[0], handler, height);
+ emit(c, e->line, e->column, I64_FROM, 0, 0);
+ }
+ return (Kind){I64, -1};
+ }
+ if (e->op == NUM_FROM) { /* number(i): exact within 2^53 */
+ if (loose(e->args[0])) return value(c, e->args[0], handler, height);
+ integer(c, e->args[0], handler, height);
+ emit(c, e->line, e->column, NUM_FROM, 0, 0);
+ return (Kind){NUM, -1};
+ }
+ if (e->op == NEG) {
+ Kind operand = wanted(c, e->args[0], handler, height, want);
+ if (operand.type == I64) {
+ emit(c, e->line, e->column, I64_NEG, 0, 0);
+ return operand;
+ }
+ expect_kind(c, e->args[0], operand, NUM);
+ emit(c, e->line, e->column, NEG, 0, 0);
+ return (Kind){NUM, -1};
}
+ if (e->count == 2 && (e->op == MUL || e->op == DIV || e->op == MOD || e->op == MIN || e->op == MAX)) {
+ Kind left = wanted(c, e->args[0], handler, height, left_hint(c, e, handler, height, want));
+ Type type = second(c, e, left, handler, height);
+ emit(c, e->line, e->column, type == I64 ? i64_op(e->op) : (uint8_t)e->op, 0, 0);
+ return (Kind){type, -1};
+ }
+ for (unsigned i = 0; i < e->count; ++i) number(c, e->args[i], handler, height + (int)i);
emit(c, e->line, e->column, (uint8_t)e->op, 0, 0);
- return (Kind){logic ? BOOL : NUM, -1};
+ return (Kind){NUM, -1};
}
}
}
@@ -2332,10 +2539,11 @@ static void record_fields(Compiler *c, const Expr *e, int list, int height) {
record_values(c, e, list, values);
for (unsigned k = 0; k < r->fields; ++k) {
int at = height + (int)k;
- if (values[k]) expect_kind(c, values[k], value(c, values[k], true, at), field_kind(r, k));
+ if (values[k]) expect_kind(c, values[k], wanted(c, values[k], true, at, field_kind(r, k)), field_kind(r, k));
else if (at + 1 > STACK) fail_at(c, e->line, e->column, "expression needs more than 64 stack values");
- else if (r->field_text[k]) emit(c, e->line, e->column, TEXT_CONST, r->field_constant[k], 0);
- else emit(c, e->line, e->column, CONST, r->field_bool[k], r->field_number[k]);
+ else if (r->field_kinds[k] == TEXT) emit(c, e->line, e->column, TEXT_CONST, r->field_constant[k], 0);
+ else if (r->field_kinds[k] == I64) emit_integer(c, e->line, e->column, r->field_integer[k]);
+ else emit(c, e->line, e->column, CONST, r->field_kinds[k] == BOOL, r->field_number[k]);
}
}
@@ -2380,8 +2588,8 @@ static void statements(Compiler *c, const Stmt *s) {
fail_at(c, s->line, s->column, "'%s' is the item this for walks", s->name);
if (c->local_slots == LOCALS) fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS);
Kind held = value(c, s->value, true, 0);
- if (held.type != NUM && held.type != BOOL && held.type != INDEX)
- fail_at(c, s->value->line, s->value->column, "a let holds a number, a bool or an index, not %s",
+ if (held.type != NUM && held.type != BOOL && held.type != I64 && held.type != INDEX)
+ fail_at(c, s->value->line, s->value->column, "a let holds a number, a bool, an i64 or an index, not %s",
type_name(held.type));
unsigned local = c->local_count++;
memcpy(c->locals[local].name, s->name, NAME);
@@ -2393,7 +2601,7 @@ static void statements(Compiler *c, const Stmt *s) {
case S_ASSIGN: {
int local = local_named(c, s->name);
if (local >= 0) { /* a let takes a new value of its kind */
- Kind kind = value(c, s->value, true, 0);
+ Kind kind = wanted(c, s->value, true, 0, c->locals[local].kind.type);
if (kind.type != c->locals[local].kind.type || (kind.type == INDEX && kind.owner != c->locals[local].kind.owner))
fail_at(c, s->value->line, s->value->column, "'%s' holds %s", s->name,
c->locals[local].kind.type == INDEX ? "an index of its list" : type_name(c->locals[local].kind.type));
@@ -2406,7 +2614,10 @@ static void statements(Compiler *c, const Stmt *s) {
if (kind) fail_at(c, s->line, s->column, "cannot assign to %s '%s'", kind, s->name);
fail_at(c, s->line, s->column, "unknown state '%s'", s->name);
}
- if (c->state_types[index] == STATE_NUM || c->state_types[index] == STATE_BOOL) {
+ if (c->state_types[index] == STATE_I64) {
+ integer(c, s->value, true, 0);
+ emit(c, s->line, s->column, STORE, (uint32_t)index, 0);
+ } else if (c->state_types[index] == STATE_NUM || c->state_types[index] == STATE_BOOL) {
expect_kind(c, s->value, value(c, s->value, true, 0), c->state_types[index] == STATE_BOOL ? BOOL : NUM);
emit(c, s->line, s->column, STORE, (uint32_t)index, 0);
} else if (c->state_types[index] == STATE_LIST) {
@@ -2493,7 +2704,7 @@ static void statements(Compiler *c, const Stmt *s) {
for (unsigned k = 0; k < r->fields; ++k) {
if ((int)k == key) continue;
item_of(c, s->first, true, 0, list);
- expect_kind(c, values[k], value(c, values[k], true, 1), field_kind(r, k));
+ expect_kind(c, values[k], wanted(c, values[k], true, 1, field_kind(r, k)), field_kind(r, k));
emit(c, s->line, s->column, ITEM_SET, (uint32_t)list | k << 16, 0);
}
break;
@@ -2508,7 +2719,7 @@ static void statements(Compiler *c, const Stmt *s) {
if (c->list_key[list] == (int)field + 1)
fail_at(c, s->line, s->column, "the key of '%s' does not change; remove the item and add it again", c->state_names[list]);
if (*s->name) item_of(c, s->first, true, 0, list);
- expect_kind(c, s->value, value(c, s->value, true, 1), field_kind(r, field));
+ expect_kind(c, s->value, wanted(c, s->value, true, 1, field_kind(r, field)), field_kind(r, field));
emit(c, s->line, s->column, ITEM_SET, (uint32_t)list | field << 16, 0);
break;
}
@@ -2538,7 +2749,7 @@ static void statements(Compiler *c, const Stmt *s) {
if (!given[f]) fail_at(c, s->line, s->column, "send %s needs '%s'", s->name, out->field_names[f]);
int type = out->field_types[f];
if (type == FIELD_BYTES) bytes_value(c, given[f], (int)f, "a bytes field");
- else expect_kind(c, given[f], value(c, given[f], true, (int)f), message_kind(type));
+ else expect_kind(c, given[f], wanted(c, given[f], true, (int)f, message_kind(type)), message_kind(type));
}
emit(c, s->line, s->column, SEND, (uint32_t)output, 0);
break;
@@ -2561,10 +2772,12 @@ static void statements(Compiler *c, const Stmt *s) {
const char *kind = declared_kind(c, s->name);
if (kind) fail_at(c, s->last->line, s->last->column, "'%s' is already declared as %s", s->name, kind);
if (s->low) { /* a slice: the keys from low up to before high */
- if (!keyed_by(c, list) || field_kind(record_of(c, list), (unsigned)c->list_key[list] - 1) != NUM)
- fail_at(c, s->first->line, s->first->column, "'%s' is not keyed by a number: a slice takes one", s->member);
- number(c, s->low, true, 0);
- number(c, s->high, true, 1);
+ Type key = keyed_by(c, list) ? field_kind(record_of(c, list), (unsigned)c->list_key[list] - 1) : TEXT;
+ if (key != NUM && key != I64)
+ fail_at(c, s->first->line, s->first->column, "'%s' is not keyed by a number or an i64: a slice takes one",
+ s->member);
+ expect_kind(c, s->low, wanted(c, s->low, true, 0, key), key);
+ expect_kind(c, s->high, wanted(c, s->high, true, 1, key), key);
}
unsigned at = c->code_count, jump = 0;
emit(c, s->line, s->column, EACH, 0, 0);
@@ -2621,6 +2834,11 @@ static unsigned char *put32(unsigned char *p, uint32_t v) {
return p + 4;
}
+static unsigned char *put64(unsigned char *p, uint64_t v) {
+ p = put32(p, (uint32_t)v);
+ return put32(p, (uint32_t)(v >> 32));
+}
+
static unsigned char *put_double(unsigned char *p, double value) {
uint64_t bits;
memcpy(&bits, &value, sizeof(bits));
@@ -2688,7 +2906,7 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
if (*c->state_key[i]) {
const Record *r = &c->records[record];
c->list_key[i] = 1 + (int)record_field(c, r, c->state_key[i], c->state_key_line[i], c->state_key_column[i]);
- if (r->field_bool[c->list_key[i] - 1])
+ if (r->field_kinds[c->list_key[i] - 1] == BOOL)
fail_at(c, c->state_key_line[i], c->state_key_column[i], "a list is keyed by a number or text; '%s' is a bool",
c->state_key[i]);
}
@@ -2698,7 +2916,7 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
Record *r = &c->records[i];
for (unsigned k = 0; k < r->fields; ++k) {
Expr empty = {.kind = E_TEXT, .line = r->line, .column = r->column};
- if (r->field_text[k]) r->field_constant[k] = constant(c, r->field_string[k] ? r->field_string[k] : &empty);
+ if (r->field_kinds[k] == TEXT) r->field_constant[k] = constant(c, r->field_string[k] ? r->field_string[k] : &empty);
}
}
for (unsigned i = 0; i < c->handler_count; ++i) {
@@ -2764,10 +2982,10 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
c->arguments = c->view_arguments[c->row >= 0 ? (unsigned)c->row : i] - 1;
c->view_code[i][0] = c->code_count;
Kind kind = value(c, c->view_value[i], false, 0);
- if (kind.type != NUM && kind.type != BOOL && kind.type != TEXT)
- fail_at(c, c->view_value[i]->line, c->view_value[i]->column, "a %s is a number, a bool or text, not %s",
+ if (kind.type != NUM && kind.type != BOOL && kind.type != I64 && kind.type != TEXT)
+ fail_at(c, c->view_value[i]->line, c->view_value[i]->column, "a %s is a number, a bool, an i64 or text, not %s",
c->row >= 0 ? "column" : "view", type_name(kind.type));
- c->view_types[i] = kind.type == TEXT ? VIEW_TEXT : kind.type == BOOL ? VIEW_BOOL : VIEW_NUM;
+ c->view_types[i] = kind.type == TEXT ? VIEW_TEXT : kind.type == BOOL ? VIEW_BOOL : kind.type == I64 ? VIEW_I64 : VIEW_NUM;
c->view_code[i][1] = c->code_count - c->view_code[i][0];
}
c->row = -1;
@@ -2822,6 +3040,7 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
p = put32(p, state_name[i]);
p = put32(p, (uint32_t)c->state_types[i]);
if (c->state_types[i] == STATE_NUM || c->state_types[i] == STATE_BOOL) p = put_double(p, c->initial[i]);
+ else if (c->state_types[i] == STATE_I64) p = put64(p, (uint64_t)c->state_integer[i]);
else { /* flags: a mark (1: the cursor stays), or a keyed list's key field + 1 */
p = put32(p, state_ref[i]);
p = put32(p, c->state_types[i] == STATE_LIST ? (c->state_rows[i] ? LIST_ROWS : (uint32_t)c->list_key[i]) :
@@ -2861,8 +3080,10 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
for (unsigned k = 0; k < c->records[i].fields; ++k) {
const Record *r = &c->records[i];
p = put32(p, record_field_name[i][k]);
- p = put32(p, r->field_text[k] ? 1 : r->field_bool[k] ? FIELD_RECORD_BOOL : 0);
- if (r->field_text[k]) { p = put32(p, r->field_constant[k]); p = put32(p, 0); }
+ static const uint32_t types[] = {[NUM] = 0, [TEXT] = 1, [BOOL] = 2, [I64] = 3}; /* Pure's record field types */
+ p = put32(p, types[r->field_kinds[k]]);
+ if (r->field_kinds[k] == TEXT) { p = put32(p, r->field_constant[k]); p = put32(p, 0); }
+ else if (r->field_kinds[k] == I64) p = put64(p, (uint64_t)r->field_integer[k]);
else p = put_double(p, r->field_number[k]);
}
for (unsigned i = 0; i < c->views; ++i) {
@@ -2881,7 +3102,7 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
for (unsigned i = 0; i < c->code_count; ++i) {
*p++ = c->code[i].op; *p++ = 0; *p++ = 0; *p++ = 0;
p = put32(p, c->code[i].arg);
- p = put_double(p, c->code[i].value);
+ p = put64(p, (uint64_t)c->code[i].integer); /* a number's bits, or an i64's */
}
memcpy(p, strings, used);
if (c->constant_used) memcpy(p + used, c->constant_data, c->constant_used);
diff --git a/docs/design/001-language-v0.md b/docs/design/001-language-v0.md
index c54fa7f..0da96d6 100644
--- a/docs/design/001-language-v0.md
+++ b/docs/design/001-language-v0.md
@@ -1,6 +1,6 @@
# aup v0 — 한 언어, 한 모듈 형식
-2026-09-25. 상태: v0a(scalar), v0b(바이트·영속 문서·text·커서), v0c(레코드·리스트·인덱스·조건식·표 보기·fn·use·`bool`) 구현.
+2026-09-25. 상태: v0a(scalar), v0b(바이트·영속 문서·text·커서), v0c(레코드·리스트·인덱스·조건식·표 보기·fn·use·`bool`·`i64`) 구현.
text와 리스트 문법은 [002](002-dsl-direction.md).
착수 기준:
@@ -113,7 +113,7 @@ on edit {
```
- `on 메시지 { }`는 메시지가 도착할 때 한 번 실행된다. 필드는 `메시지.필드`, 키는 `key`로만 쓴다.
-- 필드 타입은 `u32`, `number`(유한한 수), `bool`, `bytes`, `text`(UTF-8)이고 가변 필드(bytes, text)는 몇 개든 어디에나 둘 수 있다.
+- 필드 타입은 `u32`, `i64`, `number`(유한한 수), `bool`, `bytes`, `text`(UTF-8)이고 가변 필드(bytes, text)는 몇 개든 어디에나 둘 수 있다.
필드는 쉼표나 줄바꿈(또는 세미콜론)으로 나눈다. 메시지와 그룹과 레코드의 선언에서도, 값(`Kept { id: 1, t: "a" }`)에서와 같다.
`input add = message { title: text, points: number }`. HTTP 호스트(`pure-serve`)는 JSON 본문의 객체를 이 필드들로
옮긴다. 도착한 메시지의 text가 UTF-8이 아니거나 수가 유한하지 않으면 그 이벤트는 실패한다.
@@ -165,3 +165,31 @@ view status = "{on}" # true 또는 false
- 모듈을 바꾸면 수였던 상태나 필드가 bool이 될 때 0과 1은 `false`와 `true`가 되고, 다른 수는 새 기본값이 된다. bool이 수가 되면
0과 1이다.
+## i64 (구현)
+
+정확한 64비트 정수다. 수(binary64)는 2^53을 넘는 정수를 정확히 담지 못한다. 밖에서 오는 ID, 나노초, 큰 누계는 i64로 둔다
+(Pure [016](/srv/git/pure.git): `docs/design/016-bool-and-i64.md`).
+
+```text
+record Order { id: i64, total: i64 = 0 }
+state orders: list of Order by id
+state next: i64 = 9007199254740993 # 2^53 + 1, 정확하게
+input put = message { id: i64 } # JSON과 쿼리에서 정수의 자릿수만
+
+on put { orders.add(Order { id: put.id }) }
+view sum = sum(o.total for o in orders)
+view next_text = "{next}" # 모든 자릿수
+```
+
+- 상태 `state n: i64`(0부터) 또는 `state n: i64 = 5`, 레코드 필드 `id: i64`(`= 5`도), 메시지·출력·인자의 필드 `id: i64`,
+ `let`, 보기와 열이 i64가 될 수 있다. 키 있는 목록의 키(`by id`)와 조각(`xs[lo .. hi]`)도 된다.
+- 정수 리터럴(점과 지수가 없는 자릿수, 부호가 붙은 것도)은 문맥의 타입이 된다. 연산이나 비교의 다른 쪽이 i64이거나,
+ i64 상태·필드·인자·키·보냄에 주면 i64다. 리터럴끼리의 식(`60 * 60 * 1000`)도 다른 쪽을 따른다. 문맥이 없으면 수다.
+- `+ - *`와 단항 `-`는 범위를 넘으면 사건이 `overflow`로 실패한다. `/`와 `%`는 내림한다(나머지는 나누는 수의 부호를
+ 따른다). 0으로 나누면 `divide`다.
+- 수와 섞지 않는다. `i64(x)`는 x가 정수이고 범위 안일 때, `number(i)`는 |i| ≤ 2^53일 때만 되고, 아니면 사건이 `limit`으로
+ 실패한다. 진단이 섞인 자리를 가리키고 변환을 권한다(`an i64 and a number do not mix: convert one, as in i64(x) or
+ number(n)`).
+- `min`·`max`, 비교, 집계(`sum`은 넘치면 `overflow`, `max`·`min`, `distinct`)가 된다. `clamp`와 `floor`는 수만 받는다.
+- 보간과 보기는 모든 자릿수를 쓴다. 모듈을 바꾸면 수와 i64는 값이 맞을 때 서로 옮겨지고, 아니면 기본값이 된다.
+
diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md
index b0fd8ba..eebda2a 100644
--- a/docs/design/002-dsl-direction.md
+++ b/docs/design/002-dsl-direction.md
@@ -68,7 +68,7 @@ view current = note.line(at) # 커서가 있는 줄. 복사하지 않�
|---|---|---|
| v0a ✅ | 스칼라 상태, 키 입력, 핸들러, 보기 | 워크벤치 엔진과 비트 단위 일치 |
| v0b ✅ | `bytes`, 키별 영속 문서, `message` 입력, 응답, `require`, `text`(리터럴·보간·`cursor in`·`char`/`line`), `text` 입력 | 기존 편집기 CLI와 바이트 단위 일치, text 편집이 참조 모델과 일치 |
-| v0c | `record`, `list of`, `index of`, 조건식, 항목 투영(`each`), 순수 `fn`, `use` 라이브러리, 집계, 키 있는 리스트(`map`의 자리), text 비교, 걷기(`for`)와 메시지의 행, `bool` ✅ / `i64` | 모든 연산에 실행량 상한, 리스트가 참조 모델과 일치 ✅ |
+| v0c | `record`, `list of`, `index of`, 조건식, 항목 투영(`each`), 순수 `fn`, `use` 라이브러리, 집계, 키 있는 리스트(`map`의 자리), text 비교, 걷기(`for`)와 메시지의 행, `bool`, `i64` ✅ | 모든 연산에 실행량 상한, 리스트가 참조 모델과 일치 ✅ |
| v0d ✅ | `use vega`로 첫 인터페이스 투영(목록·표·커서·톤), [Vega 001](/srv/git/vega.git) | 한 명세가 세 표면(TUI·CLI·NUI 문장)에서 동작, 표면은 보이는 칸만 계산 |
### 구현된 text (v0b)
@@ -372,7 +372,7 @@ view branch(b: u32, over: number) = each a in accounts where a.bid == b && a.bal
}
```
-- **보기는 인자를 받는 함수다.** `view 이름(인자: 타입, …) = …`의 인자는 u32, number, bool, bytes, text이고, 보기 안에서(표라면 그 필터와
+- **보기는 인자를 받는 함수다.** `view 이름(인자: 타입, …) = …`의 인자는 u32, i64, number, bool, bytes, text이고, 보기 안에서(표라면 그 필터와
열, 그 안의 집계에서도) 이름으로 읽는다. HTTP로는 `GET /views/balance?aid=42`, 표는 `?b=1&over=0&from=20&count=20`이다. 등록한
질의(`aup query`)도 인자를 받는다. 인자 이름은 상태나 다른 선언, 표의 행과 겹칠 수 없고 `from`·`count`는 쪽 나누기의 몫이다.
- 모듈에서는 `balance.args`라는 인자 입력이 보기를 가리키고, 보기는 메시지 핸들러가 필드를 읽듯 인자를 읽는다(Pure 008).
diff --git a/tests/check.py b/tests/check.py
index 3100e64..df670ee 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -91,7 +91,7 @@ DIAGNOSTICS = [
# v0b: messages, keyed documents and types
('state d: bytes by key\ninput m = message\non m {\n reply d[key]\n}\n', '4:9', 'd[key] needs the handler of a message by key'),
('input m = message by key { a: u32 }\non m {\n reply m.a\n}\n', '3:9', 'reply takes bytes or text, not a number'),
- ('input m = message {\n a: float\n}\n', '2:6', 'field types are u32, number, bool, bytes, text and list of a record'),
+ ('input m = message {\n a: float\n}\n', '2:6', 'field types are u32, i64, number, bool, bytes, text and list of a record'),
('input m = message by key\non m.down {\n}\n', '2:4', "'m' is a message; write on m { }"),
('input k = key Space\non k {\n}\n', '2:4', "'k' is a key input; write on k.down or on k.up"),
('state d: bytes by key\ninput m = message by key { a: bytes }\non m {\n d[1].replace(0 .. 0, m.a)\n}\n', '4:5',
@@ -129,8 +129,8 @@ DIAGNOSTICS = [
'6:12', "a cursor of 'a' cannot point into 'b'"),
('state a: text\nstate at: cursor in a\ninput k = key KeyK\non k.down {\n at = 3\n}\n', '5:8',
'expected a cursor, not a number'),
- ('state a: text\nstate at: cursor in a\nview v = at\n', '3:10', 'a view is a number, a bool or text, not a cursor'),
- ('state a: text\nstate at: cursor in a\nview v = "{at}"\n', '3:12', 'a string shows numbers, bools and text, not a cursor'),
+ ('state a: text\nstate at: cursor in a\nview v = at\n', '3:10', 'a view is a number, a bool, an i64 or text, not a cursor'),
+ ('state a: text\nstate at: cursor in a\nview v = "{at}"\n', '3:12', 'a string shows numbers, bools, i64s and text, not a cursor'),
('state x = 1\nstate t: text = "{x}"\n', '2:17', 'a text state starts from a plain string'),
('view v = "a\\qb"\n', '1:12', 'unknown escape'),
('view v = "\\u{D800}"\n', '1:11', 'Unicode scalar'),
@@ -145,7 +145,7 @@ DIAGNOSTICS = [
# records, lists and indices
('record T {\n a = 0\n a = 1\n}\n', '3:3', "duplicate field 'a'"),
('record T {\n}\n', '1:8', 'a record needs at least one field'),
- ('record T {\n a: number\n}\n', '2:6', 'a field is a number (field = 0), a bool (field = false) or text (field: text)'),
+ ('record T {\n a: number\n}\n', '2:6', 'a field is a number (field = 0), a bool (field = false), text (field: text) or an i64 (field: i64)'),
('record T {\n a = 0\n}\nstate T = 0\n', '4:7', "'T' is already declared as a record"),
('state xs: list of Missing\n', '1:7', "a list holds a record; 'Missing' is not one"),
('state x = 0\nstate at: index of x\n', '2:7', "an index points into a list state; 'x' is not one"),
@@ -156,7 +156,7 @@ DIAGNOSTICS = [
(RECORD + 'state xs: list of T\nstate at: index of xs\nview v = xs[at]\n', '6:10', 'read a field of the item'),
(RECORD + 'state xs: list of T\nview v = xs\n', '5:10', "'xs' is a list; read xs.count"),
(RECORD + 'state xs: list of T\nview v = xs.size\n', '5:10', "a list has count, first and last, not 'size'"),
- (RECORD + 'state xs: list of T\nstate at: index of xs\nview v = at\n', '6:10', 'a view is a number, a bool or text, not an index'),
+ (RECORD + 'state xs: list of T\nstate at: index of xs\nview v = at\n', '6:10', 'a view is a number, a bool, an i64 or text, not an index'),
(RECORD + 'state xs: list of T\nstate at: index of xs\n' + ON_K + ' at = 3\n}\n', '8:8', 'expected an index, not a number'),
(RECORD + 'state xs: list of T\n' + ON_K + ' xs = 1\n}\n', '7:3', 'a list changes by append, insert, remove'),
(RECORD + 'record U {\n a = 0\n}\nstate xs: list of T\n' + ON_K + ' xs.append(U { a: 1 })\n}\n', '10:13',
@@ -191,7 +191,7 @@ DIAGNOSTICS = [
(LIST + 'view t = each x in xs {\n a = 1\n a = 2\n}\n', '8:3', "duplicate column 'a'"),
(LIST + 'view t = each x in xs {\n a = x.a\n}\nview v = x.a\n', '9:10', "unknown state 'x'"),
(LIST + 'view t = each at in xs {\n a = 1\n}\n', '6:15', "'at' is already declared as a list index"),
- (LIST + 'view t = each x in xs {\n c = x\n}\n', '7:7', 'a column is a number, a bool or text, not an index'),
+ (LIST + 'view t = each x in xs {\n c = x\n}\n', '7:7', 'a column is a number, a bool, an i64 or text, not an index'),
('view each = 1\n', '1:6', "'each' is reserved"),
# fns
('fn f(x) = x\nview v = f(1, 2)\n', '2:10', 'f expects 1 argument'),
@@ -207,13 +207,13 @@ DIAGNOSTICS = [
(LIST + 'view v = count(x in xs where count(y in xs) > 0)\n', '6:30', 'a count, sum, max or min cannot hold another'),
(LIST + 'view v = count(x in at)\n', '6:10', "'at' is not a list"),
('record T {\n t: text\n}\nstate xs: list of T\nview v = sum(x.t for x in xs)\n', '5:14',
- 'sum, max, min and distinct take numbers, not text'),
+ 'sum, max, min and distinct take numbers or i64s, not text'),
(LIST + 'fn f(n) = count(x in xs where x.a == n)\nview v = f(1)\n', '6:38',
"'n' is a parameter outside this count, sum, max or min"),
(LIST + 'view v = sum(dt for x in xs)\n', '6:14', 'dt is not available in count, sum, max or min'),
(LIST + 'view v = sum(x.a for x xs)\n', '6:24', "expected 'in' and a list, as in sum(x in tasks)"),
(LIST + 'view w = each r in xs where r {\n n = r.a\n}\n', '6:29', 'expected a bool, not an index'),
- (LIST + 'view v = first(x in xs)\n', '6:10', 'a view is a number, a bool or text, not an index'),
+ (LIST + 'view v = first(x in xs)\n', '6:10', 'a view is a number, a bool, an i64 or text, not an index'),
(LIST + 'view w = each r in xs where count(y in xs) > 0 {\n n = r.a\n}\n', '6:29',
"a table's where cannot hold a count, sum, max or min"),
# Keyed lists: a key field of the record, placed by add, found by key, never changed.
@@ -276,7 +276,7 @@ DIAGNOSTICS = [
(ARGS + 'view v(k: u32, k: u32) = k\n', '6:16', "duplicate argument 'k'"),
(ARGS + 'view v(n: u32) = n\n', '6:8', "'n' is already declared as a state"),
(ARGS + 'view t(r: u32) = each r in xs {\n a = r.a\n}\n', '6:8', "'r' names the table's row"),
- (ARGS + 'view v(k: list) = 1\n', '6:11', 'an argument is a u32, number, bool, bytes or text'),
+ (ARGS + 'view v(k: list) = 1\n', '6:11', 'an argument is a u32, i64, number, bool, bytes or text'),
(ARGS + 'view v() = 1\n', '6:6', "'v()' takes no argument: write view v = ..."),
(ARGS + 'view averylongviewnamethatistoolong(k: u32) = k\n', '6:6',
'a view with arguments has a name of at most 26 characters'),
@@ -284,7 +284,7 @@ DIAGNOSTICS = [
# A let holds a number or an index for the rest of its block, in its handler.
(LETS + 'on k.down {\n let n = 1\n}\n', '10:7', "'n' is already declared as a state"),
(LETS + 'on k.down {\n let x = 1\n let x = 2\n}\n', '11:7', "'x' is already a let here"),
- (LETS + 'on k.down {\n let x = t\n}\n', '10:11', 'a let holds a number, a bool or an index, not text'),
+ (LETS + 'on k.down {\n let x = t\n}\n', '10:11', 'a let holds a number, a bool, an i64 or an index, not text'),
(LETS + 'on k.down {\n let x = 1\n x = xs.first\n}\n', '11:7', "'x' holds a number"),
(LETS + 'on k.down {\n let i = xs.first\n i = ys.first\n}\n', '11:7', "'i' holds an index of its list"),
(LETS + 'on k.down {\n if n > 0 {\n let x = 1\n }\n n = x\n}\n', '13:7', "unknown state 'x'"),
@@ -292,8 +292,8 @@ DIAGNOSTICS = [
(LETS + 'view v = x\non k.down {\n let x = 1\n}\n', '9:10', "unknown state 'x'"),
(LETS + 'on k.down {\n let let = 1\n}\n', '10:7', "'let' is reserved"),
# A slice takes a list keyed by a number.
- (LETS + 'view v = count(x in xs[0 .. 3])\n', '9:10', "'xs' is not keyed by a number: a slice takes one"),
- (LETS + 'on k.down {\n for x in xs[0 .. 1] {\n n = 1\n }\n}\n', '10:12', "'xs' is not keyed by a number: a slice takes one"),
+ (LETS + 'view v = count(x in xs[0 .. 3])\n', '9:10', "'xs' is not keyed by a number or an i64: a slice takes one"),
+ (LETS + 'on k.down {\n for x in xs[0 .. 1] {\n n = 1\n }\n}\n', '10:12', "'xs' is not keyed by a number or an i64: a slice takes one"),
# A bool is not a number: conditions take bools, and a number says how to become one.
('state n = 0\non frame {\n if n {\n n = 1\n }\n}\n', '3:6', 'if takes a bool, not a number: compare it, as in n != 0'),
('view v = if 1 { 2 } else { 3 }\n', '1:13', 'if takes a bool, not a number'),
@@ -310,14 +310,50 @@ DIAGNOSTICS = [
('state lit = true\nstate n = 0\non frame {\n n = lit\n}\n', '4:7', 'expected a number, not a bool'),
('state lit = true\non frame {\n lit = 1\n}\n', '3:9', 'expected a bool, not a number'),
('record T {\n a = false\n}\nstate xs: list of T by a\n', '4:24', "a list is keyed by a number or text; 'a' is a bool"),
- ('record T {\n a = false\n}\nstate xs: list of T\nview v = sum(x.a for x in xs)\n', '5:', 'sum, max, min and distinct take numbers, not a bool'),
+ ('record T {\n a = false\n}\nstate xs: list of T\nview v = sum(x.a for x in xs)\n', '5:', 'sum, max, min and distinct take numbers or i64s, not a bool'),
('record T {\n a = false\n}\nstate xs: list of T\n' + ON_K + ' xs.append(T { a: 1 })\n}\n', '7:20', 'expected a bool, not a number'),
('input m = message {\n ok: bool\n}\nstate n = 0\non m {\n n = m.ok\n}\n', '6:7', 'expected a number, not a bool'),
('state lit = true\noutput o = message {\n n: number\n}\n' + ON_K + ' send o { n: lit }\n}\n', '7:15', 'expected a number, not a bool'),
('state true = 1\n', '1:7', "'true' is reserved"),
+ # An i64 is exact and whole, and does not mix with numbers: a literal takes the type the other side has.
+ ('state n: i64 = 1.5\n', '1:16', "expected a whole number for an i64 state"),
+ ('state n: i64 = 9223372036854775808\n', '1:16', "the number is past an i64's range"),
+ ('state n: i64\nstate x = 0\nview v = n + x\n', '3:14', 'expected an i64, not a number: convert it, as in i64(x)'),
+ ('state n: i64\nstate x = 0\nview v = x + n\n', '3:14', 'an i64 and a number do not mix: convert one, as in i64(x) or number(n)'),
+ ('state n: i64\non frame {\n n = 2.5\n}\n', '3:7', 'an i64 is a whole number; this one is not'),
+ ('state n: i64\nstate x = 0\non frame {\n n = x\n}\n', '4:7', 'expected an i64, not a number: convert it, as in i64(x)'),
+ ('state n: i64\nstate x = 0\non frame {\n x = n\n}\n', '4:7', 'expected a number, not an i64'),
+ ('state n: i64\nview v = clamp(n, 0, 1)\n', '2:16', 'expected a number, not an i64'),
+ ('state n: i64\nview v = floor(n)\n', '2:16', 'expected a number, not an i64'),
+ ('state n: i64\nview v = i64(n)\n', '2:14', 'expected a number, not an i64'),
+ ('state x = 0\nview v = number(x)\n', '2:17', 'expected an i64, not a number: convert it, as in i64(x)'),
+ ('record T {\n id: i64 = 1.5\n}\n', '2:13', "expected a whole number for an i64 field's default"),
+ ('record T {\n id: i64\n}\nstate xs: list of T by id\nview v = xs[1.5].id\n', '5:13',
+ "'xs' is keyed by id, an i64: expected an i64 or an index, not a number"),
+ ('state n: i64\nview v = if true { n } else { 1.5 }\n', '2:31', 'the branches of if give an i64 and a number'),
+ ('state n: i64\nview v = n < true\n', '2:14', 'expected an i64, not a bool'),
+ ('view v = 9223372036854775808 + i64(1)\n', '1:10', "the number is past an i64's range"),
+ ('input m = message {\n id: i64\n}\nstate x = 0\non m {\n x = m.id\n}\n', '6:7', 'expected a number, not an i64'),
+ ('record T {\n a: i64\n}\nstate xs: list of T\nview v = sum(x.a for x in xs) + 1.5\n', '5:33',
+ 'an i64 is a whole number; this one is not'),
]
ACCEPTED = [
+ # Literals beside numbers and i64s, nested deep: each chain is typed in one probe (a probe per level would take 2^30).
+ 'state x = 0\nstate y: i64\nview v = ' + '1 + (' * 30 + 'x' + ')' * 30 + '\nview w = ' + '1 + (' * 30 + 'y' + ')' * 30 + '\n',
+ # i64s: states, fields, keys, slices, messages, outputs, arguments, lets, arithmetic, conditionals, folds, text.
+ 'record Order {\n id: i64\n total: i64 = -1\n note: text\n}\nstate orders: list of Order by id\n'
+ 'state next: i64 = 9007199254740993\nstate sum: i64\nstate seen = 0\ninput put = message {\n id: i64\n total: i64\n}\n'
+ 'output told = message {\n id: i64\n big: bool\n}\ninput go = key KeyG\n'
+ 'on put {\n orders.add(Order { id: put.id, total: put.total * 2 + 1, note: "{put.id}" })\n'
+ ' send told { id: put.id, big: put.total > 1000 }\n}\n'
+ 'on go.down {\n let step: = 0\n}\n'.replace(' let step: = 0\n', ' let step = next / 3 % 7 - 1\n next = next + step\n'
+ ' sum = sum + max(-step, 0) + min(1, next)\n seen = number(step) + seen\n'
+ ' for o in orders[0 .. next] where o.total != 0 {\n o.total = -o.total\n }\n if orders.has(5) { orders[5].total = 9 }\n')
+ + 'view count = count(o in orders[-5 .. 5])\nview most = max(o.total for o in orders)\nview kinds = distinct(o.total for o in orders)\n'
+ 'view pick = if seen > 0 { next } else if seen < 0 { 0 } else { -1 }\nview line = "{next}/{sum}"\n'
+ 'view from(low: i64) = sum(o.total for o in orders where o.id >= low)\nview back = i64(2.0) + i64(seen)\n'
+ 'view board = each o in orders where o.id > 0 {\n id = o.id\n total = o.total\n}\n',
# Bools: states, fields, message and output fields, arguments, lets, views, columns, interpolation, has.
'state lit = false\nstate n = 0\nrecord T {\n done = false\n title: text\n}\nstate ts: list of T\n'
'record K {\n name: text\n flag = true\n}\nstate ks: list of K by name\ninput flip = key Space\n'
@@ -508,9 +544,11 @@ KEYS = ['Space', 'Enter', 'ArrowLeft', 'ArrowRight', 'KeyA', 'KeyQ', 'Digit0', '
def random_program(rng):
- """A random well-formed program of numbers and bools: every one must compile and verify."""
- states = [(f's{i}', rng.random() < 0.3) for i in range(rng.randint(1, 6))]
- numbers, flags = [name for name, flag in states if not flag], [name for name, flag in states if flag]
+ """A random well-formed program of numbers, bools and i64s: every one must compile and verify."""
+ states = [(f's{i}', rng.choice(['number', 'number', 'bool', 'i64'])) for i in range(rng.randint(1, 6))]
+ numbers = [name for name, kind in states if kind == 'number']
+ flags = [name for name, kind in states if kind == 'bool']
+ integers = [name for name, kind in states if kind == 'i64']
inputs = [f'i{i}' for i in range(rng.randint(0, 4))]
keys = rng.sample(KEYS, len(inputs))
@@ -520,26 +558,50 @@ def random_program(rng):
return rng.choice([str(rng.choice([0, 1, 2, 0.5, 1e-3, 1e300, 7.25]))] + numbers + (['dt'] if handler else []))
if choice < 0.35:
return '-' + number(depth - 1, handler)
- if choice < 0.5:
+ if choice < 0.45:
name, count = rng.choice([('min', 2), ('max', 2), ('clamp', 3)])
return f'{name}(' + ', '.join(number(depth - 1, handler) for _ in range(count)) + ')'
+ if choice < 0.5:
+ return f'number({integer(depth - 1, handler)})'
if choice < 0.6:
return f'(if {truth(depth - 1, handler)} {{ {number(depth - 1, handler)} }} else {{ {number(depth - 1, handler)} }})'
op = rng.choice(['+', '-', '*', '/'])
return f'({number(depth - 1, handler)} {op} {number(depth - 1, handler)})'
+ def integer(depth, handler):
+ choice = rng.random()
+ if depth <= 0 or choice < 0.25:
+ literal = str(rng.choice([0, 1, 7, -3, 2**31, 2**53 + 1, 2**62, -2**62, 2**63 - 1]))
+ return rng.choice([f'i64({literal})' if not integers or rng.random() < 0.5 else literal] + integers)
+ if choice < 0.35:
+ return f'i64({number(depth - 1, handler)})'
+ if choice < 0.45:
+ return f'{rng.choice(["min", "max"])}({integer(depth - 1, handler)}, {integer(depth - 1, handler)})'
+ if choice < 0.55:
+ return f'(if {truth(depth - 1, handler)} {{ {integer(depth - 1, handler)} }} else {{ {rng.choice(["0", "-1"])} }})'
+ if choice < 0.6:
+ return '-' + integer(depth - 1, handler)
+ op = rng.choice(['+', '-', '*', '/', '%'])
+ right = str(rng.choice([1, 2, 10, -3])) if rng.random() < 0.3 else integer(depth - 1, handler)
+ return f'({integer(depth - 1, handler)} {op} {right})'
+
def truth(depth, handler):
choice = rng.random()
if depth <= 0 or choice < 0.2:
return rng.choice(['true', 'false'] + flags)
if choice < 0.35:
return '!' + truth(depth - 1, handler)
- if choice < 0.7:
+ if choice < 0.55:
op = rng.choice(['<', '<=', '>', '>=', '==', '!='])
return f'({number(depth - 1, handler)} {op} {number(depth - 1, handler)})'
+ if choice < 0.7:
+ op = rng.choice(['<', '<=', '>', '>=', '==', '!='])
+ return f'({integer(depth - 1, handler)} {op} {integer(depth - 1, handler)})'
op = rng.choice(['&&', '||', '==', '!='])
return f'({truth(depth - 1, handler)} {op} {truth(depth - 1, handler)})'
+ make = {'number': number, 'bool': truth, 'i64': integer}
+
def body(depth, indent):
lines = []
for _ in range(rng.randint(0, 3)):
@@ -551,13 +613,14 @@ def random_program(rng):
lines += body(depth - 1, indent + ' ')
lines.append(f'{indent}}}')
else:
- name, flag = rng.choice(states)
- lines.append(f'{indent}{name} = {truth(3, True) if flag else number(3, True)}')
+ name, kind = rng.choice(states)
+ lines.append(f'{indent}{name} = {make[kind](3, True)}')
return lines
- lines = [f'state {name} = {rng.choice(["true", "false"] if flag else ["0", "-1", "3.5", "+2"])}' for name, flag in states]
+ starts = {'number': ['0', '-1', '3.5', '+2'], 'bool': ['true', 'false'], 'i64': ['0', '-9', '9007199254740993']}
+ lines = [f'state {name}{": i64" if kind == "i64" else ""} = {rng.choice(starts[kind])}' for name, kind in states]
lines += [f'input {name} = key {key}' for name, key in zip(inputs, keys)]
- lines += [f'view v{i} = {truth(3, False) if rng.random() < 0.3 else number(3, False)}' for i in range(rng.randint(0, 3))]
+ lines += [f'view v{i} = {rng.choice(list(make.values()))(3, False)}' for i in range(rng.randint(0, 3))]
events = ['frame'] + [f'{name}.{edge}' for name in inputs for edge in ('down', 'up')]
for _ in range(rng.randint(0, 5)):
lines.append(f'on {rng.choice(events)} {{')
@@ -1348,6 +1411,60 @@ def bools(aup, pure_run, work):
return len(shown)
+I64_SOURCE = """record Row {
+ id: i64
+ n = 0
+}
+state rows: list of Row by id
+state next: i64 = 9007199254740990
+state n = 0
+input add = key KeyA
+input grow = key KeyG
+input half = key KeyH
+input far = key KeyF
+on add.down {
+ rows.add(Row { id: next, n: n })
+ next = next + 1
+ n = n + 1
+}
+on grow.down { next = next * 1024 }
+on half.down { next = -next / 2 }
+on far.down { n = number(next) }
+view total = sum(r.id for r in rows)
+view line = "{next} {i64(-7) / 2} {i64(-7) % 2} {i64(7) % -2} {-7 / 2}"
+view least = min(r.id for r in rows)
+"""
+
+
+def integers(aup, pure_run, work):
+ """i64s are exact past 2^53 in every output (states, items, views, text),
+ arithmetic past their range fails as overflow and changes nothing,
+ division and remainders floor, a conversion to a number fails where
+ the value does not fit, and a module replaced keeps what fits."""
+ (work / 'integers.aup').write_text(I64_SOURCE)
+ (work / 'integers2.aup').write_text('record Row {\n id = 0\n n: i64\n}\nstate rows: list of Row\nstate next = 0\nstate n: i64\n')
+ compile_module(aup, work / 'integers.aup', work / 'integers.pmod')
+ compile_module(aup, work / 'integers2.aup', work / 'integers2.pmod')
+ commands = ['show', 'down add', 'down add', 'down add', 'down add', 'down far', 'down grow', 'down half',
+ f'replace {work / "integers2.pmod"}']
+ shown = [json.loads(line) for line in run(pure_run, work / 'integers.pmod', stdin='\n'.join(commands) + '\n',
+ check=False).stdout.splitlines()]
+ start = 2**53 - 2 # four rows: the last past 2^53
+ rows = [{'id': start + k, 'n': k} for k in range(4)]
+ assert strict(shown[0]['states'], {'rows': [], 'next': start, 'n': 0}), shown[0]
+ assert shown[0]['views']['line'] == f'{start} -4 1 -1 -3.5', shown[0] # i64s floored; literals alone are numbers
+ assert strict(shown[4]['states'], {'rows': rows, 'next': start + 4, 'n': 4}), shown[4]
+ assert strict(shown[4]['views'], {'total': 4 * start + 6, 'line': f'{start + 4} -4 1 -1 -3.5', 'least': start}), shown[4]
+ assert shown[5]['fault'] == 'limit' and shown[5]['states']['n'] == 4, shown[5] # 2^53 + 2 is no number
+ assert shown[6]['fault'] == 'overflow' and shown[6]['states']['next'] == start + 4, shown[6]
+ assert shown[7]['states']['next'] == -(start + 4) // 2, shown[7] # floored, as Python's //
+ # Replaced: next, within 2^53 now, a number; n an i64; ids past 2^53 no number (the default), the rest kept.
+ final = shown[8]
+ assert strict(final['states'], {'rows': [{'id': start + k if k < 3 else 0, 'n': k} for k in range(4)],
+ 'next': -(start + 4) // 2, 'n': 4}), final
+ return len(shown)
+
+
def text_parity(aup, pure_run, work, seeds, steps=400):
module = work / 'notes.pmod'
compile_module(aup, ROOT / 'examples/notes.aup', module)
@@ -1500,6 +1617,7 @@ def main():
tasks_parity(aup, pure_run, work, range(args.text_seeds))
report['conditional_snapshots'] = conditionals(aup, pure_run, work)
report['bool_snapshots'] = bools(aup, pure_run, work)
+ report['i64_snapshots'] = integers(aup, pure_run, work)
report['time_snapshots'] = timing(aup, pure_run, work, range(args.seeds))
report['watched_builds'] = watching(aup, pure_run, work)
report['fold_snapshots'] = folds(aup, pure_run, work, range(args.seeds if hasattr(args, 'seeds') else 8))