Compare commits
2 commits
b767928130
...
1e5ed47497
Author | SHA1 | Date | |
---|---|---|---|
1e5ed47497 | |||
a9a72e8f1f |
3 changed files with 75 additions and 10 deletions
|
@ -13,6 +13,8 @@ use io;
|
||||||
mkword("def")]),
|
mkword("def")]),
|
||||||
(`#t #f`, [true, false]),
|
(`#t #f`, [true, false]),
|
||||||
(`#\a #\space #\nul`, ['a', ' ', '\0']),
|
(`#\a #\space #\nul`, ['a', ' ', '\0']),
|
||||||
|
(`"\x0a;" "\x2014;" "\x2f9f4;"`, ["\n", "—", "嶲"]),
|
||||||
|
(`#\x #\x0a; #\x2014; #\x2f9f4;`, ['x', '\n', '—', '嶲']),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let i = 0z; i < len(cases); i += 1) {
|
for (let i = 0z; i < len(cases); i += 1) {
|
||||||
|
@ -23,7 +25,16 @@ use io;
|
||||||
|
|
||||||
for (let j = 0z; j < len(cases[i].1); j += 1) {
|
for (let j = 0z; j < len(cases[i].1); j += 1) {
|
||||||
const want = cases[i].1[j];
|
const want = cases[i].1[j];
|
||||||
const have = lex(&lexer)! as token;
|
const have = match (lex(&lexer)) {
|
||||||
|
case let tok: token =>
|
||||||
|
yield tok;
|
||||||
|
case io::EOF =>
|
||||||
|
assert(false, "reached EOF");
|
||||||
|
return;
|
||||||
|
case let err: error =>
|
||||||
|
assert(false, strerror(err));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if (!tokeq(want, have)) {
|
if (!tokeq(want, have)) {
|
||||||
fmt::printfln("Case {}: {}", i, cases[i].0)!;
|
fmt::printfln("Case {}: {}", i, cases[i].0)!;
|
||||||
|
|
54
parse/lex.ha
54
parse/lex.ha
|
@ -246,6 +246,9 @@ fn scanchar(lex: *lexer) (rune | error) = {
|
||||||
unget(lex, rnn);
|
unget(lex, rnn);
|
||||||
if (isspace(rnn)) {
|
if (isspace(rnn)) {
|
||||||
return rn;
|
return rn;
|
||||||
|
} else {
|
||||||
|
if (rn == 'x') {
|
||||||
|
return scanescape2(lex);
|
||||||
} else {
|
} else {
|
||||||
memio::appendrune(&namebuf, rn)!;
|
memio::appendrune(&namebuf, rn)!;
|
||||||
memio::concat(&namebuf, scanword(lex)?)!;
|
memio::concat(&namebuf, scanword(lex)?)!;
|
||||||
|
@ -257,6 +260,7 @@ fn scanchar(lex: *lexer) (rune | error) = {
|
||||||
};
|
};
|
||||||
return lex.loc: invalid;
|
return lex.loc: invalid;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
case io::EOF =>
|
case io::EOF =>
|
||||||
return rn;
|
return rn;
|
||||||
};
|
};
|
||||||
|
@ -282,6 +286,56 @@ fn scanescape(lex: *lexer) (rune | error) = {
|
||||||
case 'v' => return '\v';
|
case 'v' => return '\v';
|
||||||
case '\\' => return '\\';
|
case '\\' => return '\\';
|
||||||
case '"' => return '"';
|
case '"' => return '"';
|
||||||
|
case 'x' => return scanescape2(lex)?;
|
||||||
|
case =>
|
||||||
|
return lex.loc: invalid;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fn scanescape2(lex: *lexer) (rune | error) = {
|
||||||
|
// This handles the `\xhh...;` family of escapes.
|
||||||
|
// It's on a separate function since both [[scanescape]] and
|
||||||
|
// [[scanchar]] make use of it. Much like how [[scanescape]] assumes
|
||||||
|
// that the backslash has already been consumed, this one assumes that
|
||||||
|
// the leading character has been consumed prior to entering this
|
||||||
|
// function.
|
||||||
|
|
||||||
|
const rn = match (nextrune(lex)?) {
|
||||||
|
case let rn: rune =>
|
||||||
|
yield rn;
|
||||||
|
case io::EOF =>
|
||||||
|
return ("escape sequence", lex.loc.0, lex.loc.1): unterminated;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buf: [6]u8 = [0...];
|
||||||
|
let buf = memio::fixed(buf);
|
||||||
|
memio::appendrune(&buf, rn)!;
|
||||||
|
|
||||||
|
let count = 1z;
|
||||||
|
for (true) {
|
||||||
|
const rn = match (nextrune(lex)?) {
|
||||||
|
case let rn: rune =>
|
||||||
|
yield rn;
|
||||||
|
case io::EOF =>
|
||||||
|
return ("escape sequence", lex.loc.0, lex.loc.1): unterminated;
|
||||||
|
};
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
if (count > 6) {
|
||||||
|
return lex.loc: invalid;
|
||||||
|
} else if (rn == ';') {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
memio::appendrune(&buf, rn)!;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const buf = memio::string(&buf)!;
|
||||||
|
|
||||||
|
return match (strconv::stou32(buf, strconv::base::HEX)) {
|
||||||
|
case let codepoint: u32 =>
|
||||||
|
return codepoint: rune;
|
||||||
case =>
|
case =>
|
||||||
return lex.loc: invalid;
|
return lex.loc: invalid;
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,10 +22,10 @@ export fn strerror(err: error) const str = {
|
||||||
match (err) {
|
match (err) {
|
||||||
case let err: invalid =>
|
case let err: invalid =>
|
||||||
return fmt::bsprintf(buf,
|
return fmt::bsprintf(buf,
|
||||||
"{}:{}: Invalid token found", err.0, err.1);
|
"Invalid token found at {}:{}", err.0, err.1);
|
||||||
case let err: unterminated =>
|
case let err: unterminated =>
|
||||||
return fmt::bsprintf(buf,
|
return fmt::bsprintf(buf,
|
||||||
"{}:{}: Unterminated {} found", err.1, err.2, err.0);
|
"Unterminated {} found at {}:{}", err.0, err.1, err.2);
|
||||||
case let err: io::error =>
|
case let err: io::error =>
|
||||||
return io::strerror(err);
|
return io::strerror(err);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue