317 lines
9.7 KiB
C
317 lines
9.7 KiB
C
/*
|
|
* Shape rows compare astprint output against a substring, not a full
|
|
* golden, so tests stay readable without binding to every position
|
|
* field.
|
|
*/
|
|
#include "ww.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "wwtestpkg.h"
|
|
|
|
static int
|
|
must_parse(const char *src)
|
|
{
|
|
Arena *a = newarena();
|
|
Lex l;
|
|
Parser p;
|
|
char *ws = wwtest_wrap(src);
|
|
lexinit(&l, a, "<test>", ws, strlen(ws));
|
|
parserinit(&p, a, &l);
|
|
Node *n = parsefile(&p);
|
|
int ok = (n != NULL && p.errs == 0 && l.errs == 0);
|
|
free(ws);
|
|
freearena(a);
|
|
return ok;
|
|
}
|
|
|
|
static char *
|
|
parse_to_str(const char *src, int *errs)
|
|
{
|
|
Arena *a = newarena();
|
|
Lex l;
|
|
Parser p;
|
|
char *ws = wwtest_wrap(src);
|
|
lexinit(&l, a, "<test>", ws, strlen(ws));
|
|
parserinit(&p, a, &l);
|
|
Node *n = parsefile(&p);
|
|
*errs = p.errs + l.errs;
|
|
|
|
char *buf = NULL;
|
|
size_t len = 0;
|
|
FILE *f = open_memstream(&buf, &len);
|
|
if (f == NULL) {
|
|
free(ws);
|
|
freearena(a);
|
|
return NULL;
|
|
}
|
|
astprint(f, n);
|
|
fclose(f);
|
|
|
|
char *out = malloc(len + 1);
|
|
memcpy(out, buf, len);
|
|
out[len] = '\0';
|
|
free(buf);
|
|
free(ws);
|
|
freearena(a);
|
|
return out;
|
|
}
|
|
|
|
static int
|
|
must_contain(const char *src, const char *needle)
|
|
{
|
|
int errs;
|
|
char *got = parse_to_str(src, &errs);
|
|
if (got == NULL || errs > 0) {
|
|
fprintf(stderr, "parse errs=%d:\n%s\n", errs, src);
|
|
free(got);
|
|
return 0;
|
|
}
|
|
if (strstr(got, needle) == NULL) {
|
|
fprintf(stderr, "shape mismatch:\n src: %s\n"
|
|
" want: %s\n got:\n%s\n", src, needle, got);
|
|
free(got);
|
|
return 0;
|
|
}
|
|
free(got);
|
|
return 1;
|
|
}
|
|
|
|
static char *
|
|
imports_to_str(const char *src, int *errs, const char **pkg)
|
|
{
|
|
Arena *a = newarena();
|
|
Lex l;
|
|
Parser p;
|
|
lexinit(&l, a, "imports.ww", src, strlen(src));
|
|
parserinit(&p, a, &l);
|
|
Node *n = parseimports(&p);
|
|
*errs = p.errs + l.errs;
|
|
*pkg = n->module ? strdup(n->module) : NULL;
|
|
|
|
char *buf = NULL;
|
|
size_t len = 0;
|
|
FILE *f = open_memstream(&buf, &len);
|
|
if (f != NULL) {
|
|
astprint(f, n);
|
|
fclose(f);
|
|
}
|
|
freearena(a);
|
|
return buf;
|
|
}
|
|
|
|
static int
|
|
must_import_shape(const char *src, int imports_only, const char *binding,
|
|
const char *path, const char *alias)
|
|
{
|
|
Arena *a = newarena();
|
|
Lex l;
|
|
Parser p;
|
|
char *wrapped = imports_only ? strdup(src) : wwtest_wrap(src);
|
|
lexinit(&l, a, imports_only ? "imports.ww" : "<test>", wrapped,
|
|
strlen(wrapped));
|
|
parserinit(&p, a, &l);
|
|
Node *file = imports_only ? parseimports(&p) : parsefile(&p);
|
|
Node *u = file ? file->list : NULL;
|
|
while (u != NULL && u->kind != N_USE) u = u->next;
|
|
int ok = p.errs == 0 && l.errs == 0 && u != NULL
|
|
&& u->str != NULL && strcmp(u->str, binding) == 0
|
|
&& u->usesource != NULL && strcmp(u->usesource, path) == 0
|
|
&& u->usepath != NULL && strcmp(u->usepath, path) == 0
|
|
&& ((alias == NULL && u->usealias == NULL)
|
|
|| (alias != NULL && u->usealias != NULL
|
|
&& strcmp(u->usealias, alias) == 0))
|
|
&& u->usepkgname == NULL && u->pos.file != NULL
|
|
&& u->pos.line > 0 && u->pos.col > 0;
|
|
if (!ok)
|
|
fprintf(stderr, "import shape mismatch (%s): %s\n",
|
|
imports_only ? "imports-only" : "full", src);
|
|
free(wrapped);
|
|
freearena(a);
|
|
return ok;
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
int fail = 0;
|
|
|
|
const char *parses[] = {
|
|
"import io;",
|
|
"import io.bufio;",
|
|
"import stable io.bufio;",
|
|
"import _ io.bufio;",
|
|
"def MAX: i32 = 4096;",
|
|
"export def MAX: i32 = 4096;",
|
|
"type point = struct { x: i32, y: i32 };",
|
|
"type stream = fn(b: []u8) i32;",
|
|
"type chans = chan i32;",
|
|
"type box = struct { p: *point, n: i32, items: []i32 };",
|
|
"type arr = [16]u8;",
|
|
|
|
"fn nop() void = {};",
|
|
"fn init() void = {}; fn init() void = {};",
|
|
"export fn id(x: i32) i32 = { return x; };",
|
|
"fn add(a: i32, b: i32) i32 = { return a + b; };",
|
|
"@symbol(\"malloc\") fn cmalloc(n: u64) *void;",
|
|
"fn varadic(a: i32, ...) void;",
|
|
|
|
"fn f() void = { let x: i32 = 0; let y = 1.5; let s: str = \"hi\"; };",
|
|
"fn f() void = { if (x > 0) { return; } else { x += 1; }; };",
|
|
"fn f() void = { for (let i: i32 = 0; i < 10; i += 1) { x += i; }; };",
|
|
"fn f() void = { for (i < 10) { i += 1; }; };",
|
|
"fn f() void = { for () { i += 1; }; };",
|
|
"fn f() void = { defer free(p); };",
|
|
"fn f() void = { switch (x) { case 1, 2: y = 1; case: y = 0; }; };",
|
|
|
|
"fn ptr(p: *point) i32 = { return p.x; };",
|
|
"fn cast() void = { let x = (5 + 1): i64; };",
|
|
"fn deref(p: *i32) i32 = { return *p; };",
|
|
"fn addr(x: i32) *i32 = { return &x; };",
|
|
"fn lit() void = { let p: point = point { x = 1, y = 2 }; };",
|
|
"fn pkg() void = { fmt.println(1); };",
|
|
"fn arr() void = { let a = [1, 2, 3]; };",
|
|
"fn idx() i32 = { return a[3]; };",
|
|
"fn neg() i32 = { return -a + ~b * !c; };",
|
|
|
|
"fn ops() void = { x = 1; x += 1; x -= 1; x *= 2; x /= 2; x %= 2; "
|
|
"x &= 1; x |= 1; x ^= 1; x <<= 1; x >>= 1; };",
|
|
"fn cmp() bool = { return a == b && c != d || e < f && g <= h; };",
|
|
"fn bits() i32 = { return (a & b) | (c ^ d); };",
|
|
"fn shift() i32 = { return a << 2 | b >> 1; };",
|
|
|
|
"fn nested() void = { if (a > 0) { if (b > 0) { c = 1; }; }; };",
|
|
"fn many(a: i32, b: i32, c: i32, d: i32, e: i32) i32 = "
|
|
"{ return a + b * c - d / e; };",
|
|
"fn slc(s: []u8) []u8 = { return s; };",
|
|
"fn ssn(p: **point) i32 = { return (*p).x; };",
|
|
"fn arrptr(p: *[16]u8) u8 = { return p[0]; };",
|
|
"fn fnt(f: fn(i32) i32, x: i32) i32 = { return f(x); };",
|
|
"fn anontype() void = { let f: fn(i32) i32 = id; };",
|
|
|
|
"import io;\nimport fmt;\ndef N: i32 = 8;\ntype p = struct{x:i32};\nfn f() void = {};",
|
|
"@symbol(\"strlen\") fn cstrlen(s: *u8) u64;",
|
|
"fn f(a: i32, b: i32,) void = {};",
|
|
"fn f() void = { let p: *i32 = nil; let x: bool = true; let y: bool = false; };",
|
|
"fn ch() void = { let c: chan i32; let v = <-c; };",
|
|
"fn lit2() void = { let q = box { p = nil, n = 0, items = [1, 2] }; };",
|
|
"fn f() void = { defer close(fd); return; };",
|
|
"fn f() void = { for () { if (x) { break; }; continue; }; };",
|
|
"fn deep() i32 = { return ((((((1 + 2) * 3) - 4) / 5) % 6) << 7); };",
|
|
"fn ix() i32 = { return a[b][c[d]]; };",
|
|
"fn dot() i32 = { return a.b.c.d; };",
|
|
"fn cc() i32 = { return f()()(); };",
|
|
"fn mx() i32 = { return obj.method(arg)[idx].field; };",
|
|
|
|
"fn divmod(a: i64, b: i64) (i64, i64) = { return a / b, a % b; };",
|
|
"fn try() (i32, str) = { return 0, \"\"; };",
|
|
"fn use_tuple() void = { let q, r = divmod(10, 3); };",
|
|
"fn assign_tuple() void = { q, r = divmod(10, 3); };",
|
|
|
|
/* Hare-style type test / type assertion (postfix) */
|
|
"fn ti(r: (i64 | i32)) bool = { return r is i64; };",
|
|
"fn ai(r: (i64 | i32)) i64 = { return r as i64; };",
|
|
"fn br(r: (i64 | str)) i32 = { if (r is i64) { return 1; }; return 0; };",
|
|
|
|
/* Hare-style enum types */
|
|
"type color = enum { RED, GREEN, BLUE, };",
|
|
"type mode = enum u8 { NONE = 0, READ = 1, WRITE = 2, RDWR = READ | WRITE };",
|
|
"type whence = enum i32 { SET = 0, CUR, END };",
|
|
};
|
|
int n = sizeof parses / sizeof parses[0];
|
|
for (int i = 0; i < n; i++) {
|
|
if (!must_parse(parses[i])) {
|
|
fprintf(stderr, "parse fail [%d]: %s\n", i, parses[i]);
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
if (!must_contain("import io;", "(use \"io\"")) fail++;
|
|
if (!must_contain("import stable io.bufio;", "(use \"stable\"")) fail++;
|
|
if (!must_contain("def N: i32 = 4;", "(def \"N\"")) fail++;
|
|
if (!must_contain("def N: i32 = 4;", "(int 4")) fail++;
|
|
if (!must_contain("export fn f() void = {};", "(fn \"f\" export")) fail++;
|
|
if (!must_contain("type p = struct { x: i32 };", "(typedecl \"p\"")) fail++;
|
|
if (!must_contain("fn f() i32 = { return 1; };", "(return")) fail++;
|
|
if (!must_contain("fn f() void = { x = 1; };", "(assign =")) fail++;
|
|
if (!must_contain("fn f() i32 = { return a + b; };", "(bin +")) fail++;
|
|
if (!must_contain("@symbol(\"x\") fn f() void;", "(attr \"symbol\""))fail++;
|
|
if (!must_import_shape("import io.bufio;", 0, "bufio", "io.bufio",
|
|
NULL)) fail++;
|
|
if (!must_import_shape("import stable io.bufio;", 0, "stable",
|
|
"io.bufio", "stable")) fail++;
|
|
if (!must_import_shape("package main;\nimport io.bufio;\n", 1,
|
|
"bufio", "io.bufio", NULL)) fail++;
|
|
if (!must_import_shape("package main;\nimport stable io.bufio;\n", 1,
|
|
"stable", "io.bufio", "stable")) fail++;
|
|
|
|
{
|
|
const char *src =
|
|
"package main;\n"
|
|
"import zed;\n"
|
|
"fn f() void = { let s: str = \"import fake;\"; };\n"
|
|
"/* import hidden; */\n"
|
|
"import alpha;\n";
|
|
int errs;
|
|
const char *pkg;
|
|
char *got = imports_to_str(src, &errs, &pkg);
|
|
if (errs != 0 || pkg == NULL || strcmp(pkg, "main") != 0
|
|
|| got == NULL || strstr(got, "(use \"zed\"") == NULL
|
|
|| strstr(got, "(use \"alpha\"") == NULL
|
|
|| strstr(got, "fake") != NULL || strstr(got, "hidden") != NULL) {
|
|
fprintf(stderr, "imports-only parse mismatch:\n%s\n",
|
|
got ? got : "<null>");
|
|
fail++;
|
|
}
|
|
free((void *)pkg);
|
|
free(got);
|
|
}
|
|
{
|
|
int errs;
|
|
const char *pkg;
|
|
char *got = imports_to_str("package main;\nimport ;\n", &errs,
|
|
&pkg);
|
|
if (errs == 0) {
|
|
fputs("malformed import accepted\n", stderr);
|
|
fail++;
|
|
}
|
|
free((void *)pkg);
|
|
free(got);
|
|
}
|
|
{
|
|
int errs;
|
|
const char *pkg;
|
|
char *got = imports_to_str(
|
|
"package main;\n@trace import alpha;\n", &errs, &pkg);
|
|
if (errs == 0 || got == NULL
|
|
|| strstr(got, "(use \"alpha\"") == NULL) {
|
|
fputs("attributed import was not rejected and retained\n",
|
|
stderr);
|
|
fail++;
|
|
}
|
|
free((void *)pkg);
|
|
free(got);
|
|
}
|
|
{
|
|
int errs;
|
|
const char *pkg;
|
|
char *got = imports_to_str(
|
|
"//ww:module example.main\n"
|
|
"package main;\nimport alpha;\n", &errs, &pkg);
|
|
if (errs != 0 || pkg == NULL || strcmp(pkg, "main") != 0
|
|
|| got == NULL || strstr(got, "(use \"alpha\"") == NULL) {
|
|
fputs("imports-only parse rejected module boundary\n", stderr);
|
|
fail++;
|
|
}
|
|
free((void *)pkg);
|
|
free(got);
|
|
}
|
|
|
|
if (fail) {
|
|
fprintf(stderr, "%d parse tests failed\n", fail);
|
|
return 1;
|
|
}
|
|
printf("parse: %d/%d ok + 14 shape ok\n", n, n);
|
|
return 0;
|
|
}
|