commit a9e1e74a451ab3a2df47811610840a8069a8d623
parent 81faa2d543b104b54132ba865ba9754dd7ab7b6a
Author: Bor Grošelj Simić <bor.groseljsimic@telemach.net>
Date: Thu, 24 Feb 2022 01:55:05 +0100
hare::{parse,unparse}: update for expanded import syntax
Signed-off-by: Bor Grošelj Simić <bor.groseljsimic@telemach.net>
Diffstat:
5 files changed, 235 insertions(+), 197 deletions(-)
diff --git a/hare/ast/import.ha b/hare/ast/import.ha
@@ -2,37 +2,39 @@
// (c) 2021 Drew DeVault <sir@cmpwn.com>
// (c) 2021 Eyal Sawady <ecs@d2evs.net>
-// use module;
-export type import_module = ident;
-
-// use alias = module;
-export type import_alias = struct {
- ident: ident,
- alias: str,
+// Variants of the import statement
+export type import_mode = enum uint {
+ // use module;
+ IDENT = 0,
+ // use alias = module;
+ ALIAS = 1 << 0,
+ // use module::{foo, bar, baz};
+ MEMBERS = 1 << 1,
+ // use module::*;
+ WILDCARD = 1 << 2,
};
-// use module::{foo, bar, baz};
-export type import_objects = struct {
+// An imported module.
+export type import = struct {
+ mode: import_mode,
ident: ident,
- objects: []str,
+ alias: str,
+ objects: []((str | void), str),
};
-// An imported module.
-export type import = (import_module | import_alias | import_objects);
-
// Frees resources associated with an [[import]].
export fn import_free(import: import) void = {
- match (import) {
- case let m: import_module =>
- ident_free(m: ident);
- case let a: import_alias =>
- ident_free(a.ident);
- free(a.alias);
- case let o: import_objects =>
- ident_free(o.ident);
- for (let i = 0z; i < len(o.objects); i += 1) {
- free(o.objects[i]);
+ ident_free(import.ident);
+ if (import.mode == import_mode::ALIAS) {
+ free(import.alias);
+ };
+ for (let i = 0z; i < len(import.objects); i += 1) {
+ free(import.objects[i].1);
+ match (import.objects[i].0) {
+ case void => void;
+ case let s: str =>
+ free(s);
};
- free(o.objects);
};
+ free(import.objects);
};
diff --git a/hare/module/scan.ha b/hare/module/scan.ha
@@ -365,16 +365,8 @@ fn scan_file(
let lexer = lex::init(&tee, path);
let imports = parse::imports(&lexer)?;
for (let i = 0z; i < len(imports); i += 1) {
- let ident = match (imports[i]) {
- case let m: ast::import_module =>
- yield m: ast::ident;
- case let a: ast::import_alias =>
- yield a.ident;
- case let o: ast::import_objects =>
- yield o.ident;
- };
- if (!have_ident(deps, ident)) {
- append(deps, ident);
+ if (!have_ident(deps, imports[i].ident)) {
+ append(deps, imports[i].ident);
};
};
// Finish spooling out the file for the SHA
diff --git a/hare/parse/+test/unit.ha b/hare/parse/+test/unit.ha
@@ -12,118 +12,111 @@ use io::{mode};
use io;
use strings;
-@test fn imports() void = {
- {
- const in = "use foo;";
- let buf = bufio::fixed(strings::toutf8(in), mode::READ);
- let lexer = lex::init(&buf, "<test>");
- let mods = imports(&lexer) as []ast::import;
- defer for (let i = 0z; i < len(mods); i += 1) {
- ast::import_free(mods[i]);
+fn import_eq(i1: ast::import, i2: ast::import) bool = {
+ if (i1.mode != i2.mode) {
+ return false;
+ };
+ if (!ast::ident_eq(i1.ident, i2.ident)) {
+ return false;
+ };
+ let mode = i1.mode;
+ if (mode & ast::import_mode::ALIAS != 0 && i1.alias != i2.alias) {
+ return false;
+ };
+ if (mode & ast::import_mode::MEMBERS != 0) {
+ for (let i = 0z; i < len(i1.objects); i += 1) {
+ let o1 = i1.objects[i], o2 = i2.objects[i];
+ if (o1.0 is void ^^ o2.0 is void) {
+ return false;
+ };
+ if (o1.0 is str && o1.0: str != o2.0: str) {
+ return false;
+ };
+ if (o1.1 != o2.1) {
+ return false;
+ };
};
+ };
+ return true;
+};
- assert(len(mods) == 1);
- assert(mods[0] is ast::import_module);
+type import_tuple = (ast::import_mode, ast::ident, str, []((str | void), str));
- let mod = mods[0] as ast::import_module;
- assert(len(mod) == 1 && mod[0] == "foo");
- let tok = lex::lex(&lexer) as lex::token;
- assert(tok.0 == lex::ltok::EOF);
- };
+fn tup_to_import(tup: import_tuple) ast::import = ast::import {
+ mode = tup.0,
+ ident = tup.1,
+ alias = tup.2,
+ objects = tup.3,
+ ...
+};
- {
- const in =
- "use foo;\n"
- "use bar;\n"
- "use baz::bat;\n\n"
- "export fn main() void = void;";
- let buf = bufio::fixed(strings::toutf8(in), mode::READ);
- let lexer = lex::init(&buf, "<test>");
- let mods = imports(&lexer) as []ast::import;
- defer for (let i = 0z; i < len(mods); i += 1) {
- ast::import_free(mods[i]);
- };
+@test fn imports() void = {
+ const in =
+ "use foo;\n"
+ "use bar;\n"
+ "use baz::bat;\n\n"
- assert(len(mods) == 3);
- let expected: [_][]str = [["foo"], ["bar"], ["baz", "bat"]];
+ "use foo = bar;\n"
+ "use baz = bat;\n"
+ "use qux = quux::corge;\n"
- for (let i = 0z; i < len(mods); i += 1) {
- assert(mods[i] is ast::import_module);
- let mod = mods[i] as ast::import_module;
- assert(len(mod) == len(expected[i]));
- for (let j = 0z; j < len(expected[i]); j += 1z) {
- assert(mod[j] == expected[i][j]);
- };
- };
+ "use foo::*;"
+ "use foo::bar::quux::*;"
- let tok = lex::lex(&lexer) as lex::token;
- assert(tok.0 == lex::ltok::EXPORT);
- };
+ "use foo::{bar};\n"
+ "use foo::{bar,};\n"
+ "use baz::{bat, qux};\n"
+ "use quux::corge::{grault, garply,};\n"
- {
- const in =
- "use foo = bar;\n"
- "use baz = bat;\n"
- "use qux = quux::corge;\n"
- "export fn main() void = void;";
- let buf = bufio::fixed(strings::toutf8(in), mode::READ);
- let lexer = lex::init(&buf, "<test>");
- let mods = imports(&lexer) as []ast::import;
- defer for (let i = 0z; i < len(mods); i += 1) {
- ast::import_free(mods[i]);
- };
+ "use quux::{alias = grault};\n"
+ "use quux::{alias = grault,};\n"
+ "use quux::{alias = grault, garply};\n"
+ "use quux::{alias = grault, alias2 = garply};\n"
- assert(len(mods) == 3);
- let expected: [_](str, []str) = [
- ("foo", ["bar"]),
- ("baz", ["bat"]),
- ("qux", ["quux", "corge"])
- ];
+ "use alias = quux::corge::{grault, garply,};\n"
+ "use modalias = quux::{alias = grault, alias2 = garply};\n"
- for (let i = 0z; i < len(mods); i += 1) {
- assert(mods[i] is ast::import_alias);
- let mod = mods[i] as ast::import_alias;
- assert(mod.alias == expected[i].0);
- assert(len(mod.ident) == len(expected[i].1));
- for (let j = 0z; j < len(expected[i].1); j += 1z) {
- assert(mod.ident[j] == expected[i].1[j]);
- };
- };
+ "export fn main() void = void;";
+ let buf = bufio::fixed(strings::toutf8(in), mode::READ);
+ let lexer = lex::init(&buf, "<test>");
+ let mods = imports(&lexer)!;
+ defer for (let i = 0z; i < len(mods); i += 1) {
+ ast::import_free(mods[i]);
};
- {
- const in =
- "use foo::{bar};\n"
- "use baz::{bat, qux};\n"
- "use quux::corge::{grault, garply,};\n"
- "export fn main() void = void;";
- let buf = bufio::fixed(strings::toutf8(in), mode::READ);
- let lexer = lex::init(&buf, "<test>");
- let mods = imports(&lexer) as []ast::import;
- defer for (let i = 0z; i < len(mods); i += 1) {
- ast::import_free(mods[i]);
- };
-
- assert(len(mods) == 3);
- let expected: [_]([]str, []str) = [
- (["foo"], ["bar"]),
- (["baz"], ["bat", "qux"]),
- (["quux", "corge"], ["grault", "garply"])
- ];
+ let expected: [_]import_tuple = [
+ (ast::import_mode::IDENT, ["foo"], "", []),
+ (ast::import_mode::IDENT, ["bar"], "", []),
+ (ast::import_mode::IDENT, ["baz", "bat"], "", []),
+ (ast::import_mode::ALIAS, ["bar"], "foo", []),
+ (ast::import_mode::ALIAS, ["bat"], "baz", []),
+ (ast::import_mode::ALIAS, ["quux", "corge"], "qux", []),
+ (ast::import_mode::WILDCARD, ["foo"], "", []),
+ (ast::import_mode::WILDCARD, ["foo", "bar", "quux"], "", []),
+ (ast::import_mode::MEMBERS, ["foo"], "", [(void, "bar")]),
+ (ast::import_mode::MEMBERS, ["foo"], "", [(void, "bar")]),
+ (ast::import_mode::MEMBERS, ["baz"], "", [(void, "bat"), (void, "qux")]),
+ (ast::import_mode::MEMBERS,
+ ["quux", "corge"], "", [(void, "grault"), (void, "garply")]),
+ (ast::import_mode::MEMBERS, ["quux"], "", [("alias", "grault")]),
+ (ast::import_mode::MEMBERS, ["quux"], "", [("alias", "grault")]),
+ (ast::import_mode::MEMBERS,
+ ["quux"], "", [("alias", "grault"), (void, "garply")]),
+ (ast::import_mode::MEMBERS,
+ ["quux"], "", [("alias", "grault"), ("alias2", "garply")]),
+ (ast::import_mode::MEMBERS | ast::import_mode::ALIAS,
+ ["quux", "corge"], "alias", [(void, "grault"), (void, "garply")]),
+ (ast::import_mode::MEMBERS | ast::import_mode::ALIAS,
+ ["quux"], "modalias", [("alias", "grault"), ("alias2", "garply")]),
+ ];
- for (let i = 0z; i < len(mods); i += 1) {
- assert(mods[i] is ast::import_objects);
- let mod = mods[i] as ast::import_objects;
- assert(len(mod.objects) == len(expected[i].1));
- for (let j = 0z; j < len(expected[i].0); j += 1z) {
- assert(mod.objects[j] == expected[i].1[j]);
- };
- assert(len(mod.ident) == len(expected[i].0));
- for (let j = 0z; j < len(expected[i].0); j += 1z) {
- assert(mod.ident[j] == expected[i].0[j]);
- };
- };
+ assert(len(mods) == len(expected));
+ for (let i = 0z; i < len(mods); i += 1) {
+ assert(import_eq(mods[i], tup_to_import(expected[i])));
};
+
+ let tok = lex::lex(&lexer) as lex::token;
+ assert(tok.0 == lex::ltok::EXPORT);
};
@test fn decls() void = {
diff --git a/hare/parse/import.ha b/hare/parse/import.ha
@@ -5,12 +5,21 @@ use hare::ast;
use hare::lex;
use hare::lex::{ltok};
-fn name_list(lexer: *lex::lexer) ([]str | error) = {
- let names: []str = [];
+use fmt;
+
+fn name_list(lexer: *lex::lexer) ([]((str | void), str) | error) = {
+ let names: []((str | void), str) = [];
for (true) {
- append(names, want(lexer, ltok::NAME)?.1 as str);
+ append(names, (void, want(lexer, ltok::NAME)?.1 as str));
+ match (try(lexer, ltok::EQUAL)?) {
+ case void => void;
+ case =>
+ let name = &names[len(names) - 1];
+ name.0 = name.1;
+ name.1 = want(lexer, ltok::NAME)?.1 as str;
+ };
- switch (want(lexer, ltok::COMMA, ltok::RBRACE)?.0) {
+ switch (want(lexer, ltok::EQUAL, ltok::COMMA, ltok::RBRACE)?.0) {
case ltok::COMMA =>
match (try(lexer, ltok::RBRACE)?) {
case void => void;
@@ -34,33 +43,41 @@ export fn imports(lexer: *lex::lexer) ([]ast::import | error) = {
case => void;
};
- let name = ident_trailing(lexer)?;
- switch (want(lexer, ltok::SEMICOLON, ltok::LBRACE,
- ltok::EQUAL)?.0) {
- case ltok::SEMICOLON =>
- synassert(lex::mkloc(lexer), !name.1,
- "Unexpected trailing :: in ident")?;
- append(imports, name.0: ast::import_module);
- case ltok::LBRACE =>
- synassert(lex::mkloc(lexer), name.1,
- "Expected trailing :: in ident")?;
- let objects = name_list(lexer)?;
- append(imports, ast::import_objects {
- ident = name.0,
- objects = objects,
- });
- want(lexer, ltok::SEMICOLON)?;
- case ltok::EQUAL =>
- synassert(lex::mkloc(lexer),
- len(name.0) == 1 && !name.1,
- "Expected name, not ident")?;
- let ident = ident(lexer)?;
- append(imports, ast::import_alias {
- ident = ident,
- alias = name.0[0],
- });
- want(lexer, ltok::SEMICOLON)?;
- case => abort(); // Unreachable
+ append(imports, ast::import { ... });
+ let import = &imports[len(imports) - 1];
+ for (true) {
+ let name = ident_trailing(lexer)?;
+ import.ident = name.0;
+ switch (want(lexer, ltok::SEMICOLON, ltok::LBRACE,
+ ltok::EQUAL, ltok::TIMES)?.0) {
+ case ltok::SEMICOLON =>
+ synassert(lex::mkloc(lexer), !name.1,
+ "Unexpected trailing :: in ident")?;
+ break;
+ case ltok::LBRACE =>
+ synassert(lex::mkloc(lexer), name.1,
+ "Expected trailing :: in ident")?;
+ import.mode |= ast::import_mode::MEMBERS;
+ import.objects = name_list(lexer)?;
+ want(lexer, ltok::SEMICOLON)?;
+ break;
+ case ltok::EQUAL =>
+ synassert(lex::mkloc(lexer),
+ len(name.0) == 1 && !name.1,
+ "Expected name, not ident")?;
+ import.alias = name.0[0];
+ import.mode |= ast::import_mode::ALIAS;
+ case ltok::TIMES =>
+ synassert(lex::mkloc(lexer), name.1,
+ "Expected trailing :: in ident")?;
+ synassert(lex::mkloc(lexer),
+ import.mode & ast::import_mode::ALIAS == 0,
+ "Unexpected * after aliased import")?;
+ import.mode |= ast::import_mode::WILDCARD;
+ want(lexer, ltok::SEMICOLON)?;
+ break;
+ case => abort(); // Unreachable
+ };
};
};
return imports;
diff --git a/hare/unparse/import.ha b/hare/unparse/import.ha
@@ -8,49 +8,83 @@ use hare::ast;
use strio;
// Unparses an [[ast::import]].
-export fn import(out: io::handle, i: ast::import) (size | io::error) = {
+export fn import(out: io::handle, import: ast::import) (size | io::error) = {
let n = 0z;
n += fmt::fprint(out, "use ")?;
- match (i) {
- case let m: ast::import_module =>
- n += ident(out, m)?;
- case let a: ast::import_alias =>
- n += fmt::fprint(out, a.alias, "= ")?;
- n += ident(out, a.ident)?;
- case let o: ast::import_objects =>
- n += ident(out, o.ident)?;
+ if (import.mode & ast::import_mode::ALIAS != 0) {
+ n += fmt::fprint(out, import.alias, "= ")?;
+ };
+ n += ident(out, import.ident)?;
+ if (import.mode & ast::import_mode::MEMBERS != 0) {
n += fmt::fprint(out, "::{")?;
- for (let i = 0z; i < len(o.objects); i += 1) {
- n += fmt::fprintf(out, "{}{}", o.objects[i],
- if (i + 1 < len(o.objects)) ", "
- else "")?;
+ for (let i = 0z; i < len(import.objects); i += 1) {
+ let tup = match (import.objects[i].0) {
+ case void =>
+ yield ("", "");
+ case let s: str =>
+ yield (s, " = ");
+ };
+ n += fmt::fprintf(out, "{}{}{}{}", tup.0, tup.1,
+ import.objects[i].1,
+ if (i + 1 < len(import.objects)) ", " else "")?;
};
n += fmt::fprint(out, "}")?;
+ } else if (import.mode & ast::import_mode::WILDCARD != 0) {
+ n += fmt::fprint(out, "::*")?;
};
n += fmt::fprint(out, ";")?;
return n;
};
@test fn import() void = {
- let buf = strio::dynamic();
- import(&buf, ["foo", "bar", "baz"]) as size;
- let s = strio::string(&buf);
- assert(s == "use foo::bar::baz;");
- free(s);
- buf = strio::dynamic();
- import(&buf, ast::import_alias {
- ident = ["foo"],
- alias = "bar",
- }) as size;
- s = strio::string(&buf);
- assert(s == "use bar = foo;");
- free(s);
- buf = strio::dynamic();
- import(&buf, ast::import_objects {
- ident = ["foo"],
- objects = ["bar", "baz"],
- }) as size;
- s = strio::string(&buf);
- assert(s == "use foo::{bar, baz};");
- free(s);
+ let tests: [_](ast::import, str) = [
+ (ast::import {
+ ident = ["foo", "bar", "baz"],
+ ...
+ }, "use foo::bar::baz;"),
+ (ast::import {
+ mode = ast::import_mode::ALIAS,
+ ident = ["foo"],
+ alias = "bar",
+ ...
+ }, "use bar = foo;"),
+ (ast::import {
+ mode = ast::import_mode::MEMBERS,
+ ident = ["foo"],
+ objects = [(void, "bar"), (void, "baz")],
+ ...
+ }, "use foo::{bar, baz};"),
+ (ast::import {
+ mode = ast::import_mode::WILDCARD,
+ ident = ["foo", "bar"],
+ ...
+ }, "use foo::bar::*;"),
+ (ast::import {
+ mode = ast::import_mode::MEMBERS | ast::import_mode::ALIAS,
+ ident = ["foo"],
+ alias = "quux",
+ objects = [(void, "bar"), (void, "baz")],
+ ...
+ }, "use quux = foo::{bar, baz};"),
+ (ast::import {
+ mode = ast::import_mode::MEMBERS,
+ ident = ["foo"],
+ objects = [("alias", "bar"), (void, "baz")],
+ ...
+ }, "use foo::{alias = bar, baz};"),
+ (ast::import {
+ mode = ast::import_mode::MEMBERS | ast::import_mode::ALIAS,
+ ident = ["foo"],
+ alias = "quux",
+ objects = [("alias1", "bar"), ("alias2", "baz")],
+ ...
+ }, "use quux = foo::{alias1 = bar, alias2 = baz};"),
+ ];
+ for (let i = 0z; i < len(tests); i += 1) {
+ let buf = strio::dynamic();
+ import(&buf, tests[i].0) as size;
+ let s = strio::string(&buf);
+ assert(s == tests[i].1);
+ free(s);
+ };
};