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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
use bufio;
use common;
use common::{token, ltok, nonterminal};
use fmt;
use glue;
use io;
use memio;
use strings;
// A captured variable from [[match_tokens]]
export type capture = struct {
start: common::location, // Inclusive
end: common::location, // Exclusive
kind: nonterminal,
name: str,
text: str,
data: nullable *opaque,
};
// Retrieves the text string from the source file between two locations. The
// return value is borrowed from the context.
export fn get_text(ctx: *context, start: common::location, end: common::location) str = {
const buffer = memio::buffer(ctx.buffer);
const data = buffer[start.off..end.off];
return strings::fromutf8(data)!;
};
// Retrieves the text string from the source file associated with a capture. The
// return value is borrowed from the context.
export fn capture_gettext(ctx: *context, cap: *capture) str = {
return get_text(ctx, cap.start, cap.end);
};
// Result of [[match_tokens]]
export type captures = struct {
start: common::location,
end: common::location,
vars: []capture,
};
// Retrieves a variable from a [[captures]] list.
export fn getvar(c: *captures, name: str) const *capture = {
for (let var &.. c.vars) {
if (var.name == name) {
return var;
};
};
fmt::fatalf("Invalid capture variable name '{}'", name);
};
// Frees state associated with [[captures]].
export fn captures_finish(glue: *glue::glue, c: *captures) void = {
for (let var &.. c.vars) {
free(var.name);
glue.free_nonterminal(var.kind, var.data);
};
free(c.vars);
c.vars = [];
};
// Matches a token pattern (or patterns) against a lexer.
export fn match_pattern(
ctx: *context,
vars: *captures,
patterns: str...
) (bool | common::error) = {
for (let pat .. patterns) {
const rp = ctx.glue.lex_save(ctx.lex);
captures_finish(ctx.glue, vars);
if (_match_pattern(ctx, vars, pat)?) {
free(rp);
return true;
};
ctx.glue.lex_restore(ctx.lex, rp);
};
return false;
};
fn _match_pattern(
ctx: *context,
vars: *captures,
pat: str,
) (bool | common::error) = {
const glue = ctx.glue;
const lex = ctx.lex;
vars.start = glue.lex_mkloc(lex);
defer vars.end = glue.lex_mkloc(lex);
const in = memio::fixed(strings::toutf8(pat));
const scan = bufio::newscanner(&in);
defer bufio::finish(&scan);
const ref = glue.lex_init(&scan, "<reference>");
defer glue.lex_free(ref);
for (true) {
let ref_tok = glue.lex_lex(ref)!;
switch (ref_tok.0) {
case ltok::EOF =>
break;
case ltok::DOLLAR =>
let var = parse_variable(glue, ref);
var.start = glue.lex_mkloc(lex);
if (var.name == "*") {
ref_tok = glue.lex_lex(ref)!;
assert(ref_tok.0 != ltok::EOF);
scan_until(ctx, lex, ref_tok);
continue;
};
match (glue.parse_nonterminal(lex, var.kind)) {
case let data: nullable *opaque =>
var.data = data;
var.end = glue.lex_mkloc(lex);
case common::error =>
free(var.name);
return false;
};
var.text = capture_gettext(ctx, &var);
append(vars.vars, var)!;
continue;
case => void;
};
const tok = glue.lex_lex(lex)?;
if (ref_tok.0 != tok.0) {
glue.lex_unlex(lex, tok);
return false;
};
switch (ref_tok.0) {
case ltok::NAME =>
const ref_name = ref_tok.1 as str;
const name = tok.1 as str;
if (name != ref_name) {
glue.lex_unlex(lex, tok);
return false;
};
case => void;
};
if (vars.start.line == 0) {
vars.start = tok.2;
};
};
vars.end = glue.lex_mkloc(lex);
return true;
};
fn parse_variable(
glue: *glue::glue,
ref: *glue::lexer,
) capture = {
let var = capture { ... };
let tok = glue.lex_lex(ref)!;
switch (tok.0) {
case ltok::NAME =>
var.name = strings::dup(tok.1 as str)!;
return var;
case ltok::TIMES =>
var.name = strings::dup("*")!;
return var;
case ltok::LBRACE => void;
case => abort("Invalid match pattern");
};
tok = glue.lex_lex(ref)!;
switch (tok.0) {
case ltok::NAME =>
var.name = strings::dup(tok.1 as str)!;
tok = glue.lex_lex(ref)!;
case => void;
};
switch (tok.0) {
case ltok::RBRACE =>
assert(var.name != "", "Invalid match pattern");
return var;
case ltok::COLON => void;
case => abort("Invalid match pattern");
};
tok = glue.lex_lex(ref)!;
assert(tok.0 == ltok::LIT_STR, "Invalid match pattern");
var.kind = common::str_to_nonterminal(tok.1 as str);
assert(var.kind != nonterminal::NONE, "Invalid match pattern");
tok = glue.lex_lex(ref)!;
assert(tok.0 == ltok::RBRACE, "Invalid match pattern");
return var;
};
fn scan_until(
ctx: *context,
lex: *glue::lexer,
tok: token,
) void = {
assert(tok.1 is void); // TODO?
for (true) {
let next = ctx.glue.lex_lex(lex)!;
if (next.0 == tok.0) {
break;
};
};
};
|