~smlavine/hareimports

ref: af870432bd4540efd9b9d0c7141519f58f1ed422 hareimports/main.ha -rw-r--r-- 4.7 KiB
af870432Sebastian LaVine Add path field, newimport() helper constructor 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// hareimports - manage imports for hare source files
// Copyright (C) 2022 Sebastian LaVine <mail@smlavine.com>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 only.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.

use fmt;
use hare::lex;
use io;
use os;
use strio;
use strings;

// Represents a module that has been imported into the sub-unit.
export type import = struct {
	// location of the first token in import after the use keyword
	location: lex::location,
	name: str,
	alias: str,
	// borrows the alias if there is one, otherwise borrows the name
	path: str,
	// TODO: handle second and third (unqualified) types of imports.
};

// Frees a slice of [[import]]s. See [[strings::freeall]].
export fn imports_freeall(imports: []import) void = {
	for (let i = 0z; i < len(imports); i += 1) {
		// TODO: imports[i].location?
		free(imports[i].name);
		free(imports[i].alias);
		// DON'T free .path; it is a borrow
	};
	free(imports);
};

// Returns a [[lex::syntax]] error from the location and string representation
// of the provided token.
fn badtoken(t: lex::token) lex::syntax = (t.2, lex::tokstr(t));

// Returns a [[import]] constructed from the provided parameters.
fn newimport(loc: lex::location, name: str, alias: str) import =
	import {
		location = loc,
		name = name,
		alias = alias,
		path = if (alias == "") name else alias,
	};

// Parses a single import statement from the provided [[hare::lex::lexer]].
// Precondition: the use keyword MUST have already been lexed.
// An alias will only be lexed if recursive_alias is void.
// Returns an [[import]] on success, or [[hare::lex::error]] on failure.
fn getimport(lexp: *lex::lexer, recursive_alias: str) (import | lex::error) = {
	// The first token has to be a name: either the beginning of
	// an import-alias or the beginning of an identifier.
	// See also §6.4 (Identifiers).
	const first = lex::lex(lexp)?;
	if (first.0 != lex::ltok::NAME)
		return badtoken(first);

	let module_builder = strio::dynamic();
	defer io::close(&module_builder)!;

	const second = lex::lex(lexp)?;
	switch (second.0) {
	case lex::ltok::SEMICOLON =>
		// top-level (i.e. not a submodule) import
		return newimport(
			first.2, // location
			strings::dup(lex::tokstr(first)),
			recursive_alias,
		);
	case lex::ltok::EQUAL =>
		// Import alias
		if (recursive_alias == "") {
			return getimport(lexp,
				strings::dup(lex::tokstr(first)));
		};
		return badtoken(second);
	case lex::ltok::DOUBLE_COLON =>
		strio::concat(&module_builder, lex::tokstr(first), "::")!;
	case =>
		return badtoken(second);
	};

	// The previous token was a DOUBLE_COLON.

	const third = lex::lex(lexp)?;
	switch (third.0) {
	case lex::ltok::LBRACE =>
		abort("member-list import (§6.12.4) not supported yet.");
	case lex::ltok::TIMES =>
		abort("unqualified import (§6.12.6) not supported yet.");
	case lex::ltok::NAME =>
		strio::concat(&module_builder, lex::tokstr(third))!;
	case =>
		return badtoken(third);
	};

	for (true) {
		const possible_dcolon = lex::lex(lexp)?;
		switch (possible_dcolon.0) {
		case lex::ltok::DOUBLE_COLON =>
			strio::concat(&module_builder, "::")!;
		case lex::ltok::SEMICOLON =>
			break;
		case =>
			return badtoken(possible_dcolon);
		};

		const name = lex::lex(lexp)?;
		switch (name.0) {
		case lex::ltok::NAME =>
			strio::concat(&module_builder, lex::tokstr(name))!;
		case =>
			return badtoken(name);
		};
	};

	return newimport(
		first.2,
		strings::dup(strio::string(&module_builder)),
		recursive_alias,
	);
};

// Returns a slice of [[import]]s or a [[hare::lex::error]]. The slice must be
// freed using [[imports_freeall]].
//
// See §6.12 (Units) of the Hare spec for more information.
export fn getimports(lexp: *lex::lexer) ([]import | lex::error) = {
	let imports: []import = [];
	// errdefer free(imports); // currently leaks memory on lex::error

	for (lex::lex(lexp)?.0 == lex::ltok::USE)
		append(imports, getimport(lexp, "")?);

	return imports;
};

export fn main() void = {
	const lexer = lex::init(os::stdin, "<stdin>", lex::flags::COMMENTS);

	let imports = match (getimports(&lexer)) {
	case let s: []import =>
		yield s;
	case let e: lex::error =>
		fmt::fatalf(lex::strerror(e));
	};
	defer imports_freeall(imports);

	for (let i = 0z; i < len(imports); i += 1)
		fmt::printfln("{} (= {})",
			imports[i].name, imports[i].path)!;
};