hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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:
Mhare/ast/import.ha | 50++++++++++++++++++++++++++------------------------
Mhare/module/scan.ha | 12++----------
Mhare/parse/+test/unit.ha | 189++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mhare/parse/import.ha | 79++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mhare/unparse/import.ha | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
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); + }; };