mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-24 22:11:36 +00:00
Improve unquote_string_triple to give better error messages on invalid literals
This commit is contained in:
@@ -845,6 +845,25 @@ gb_internal Ast *ast_uninit(AstFile *f, Token token) {
|
||||
}
|
||||
|
||||
gb_internal ExactValue exact_value_from_token(AstFile *f, Token const &token) {
|
||||
auto token_pos_at_offset = [](Token const &token, isize offset) -> TokenPos {
|
||||
TokenPos pos = token.pos;
|
||||
if (offset <= 0) {
|
||||
return pos;
|
||||
}
|
||||
String s = token.string;
|
||||
isize n = gb_min(offset, s.len);
|
||||
for (isize i = 0; i < n; i++) {
|
||||
if (s.text[i] == '\n') {
|
||||
pos.line += 1;
|
||||
pos.column = 1;
|
||||
} else {
|
||||
pos.column += 1;
|
||||
}
|
||||
pos.offset += 1;
|
||||
}
|
||||
return pos;
|
||||
};
|
||||
|
||||
String s = token.string;
|
||||
string_interner_insert(s);
|
||||
switch (token.kind) {
|
||||
@@ -857,8 +876,30 @@ gb_internal ExactValue exact_value_from_token(AstFile *f, Token const &token) {
|
||||
if (s.len >= 6 &&
|
||||
((s.text[0] == '"' && s.text[1] == '"' && s.text[2] == '"') ||
|
||||
(s.text[0] == '`' && s.text[1] == '`' && s.text[2] == '`'))) {
|
||||
if (!unquote_string_triple(ast_allocator(f), &s, string_contains_char(s, '\r'))) {
|
||||
syntax_error(token, "Invalid multi-line string literal");
|
||||
TripleStringErrorKind terr = TripleStringError_None;
|
||||
isize terr_off = -1;
|
||||
if (!unquote_string_triple(ast_allocator(f), &s, string_contains_char(s, '\r'), &terr, &terr_off)) {
|
||||
TokenPos pos = token_pos_at_offset(token, terr_off);
|
||||
switch (terr) {
|
||||
case TripleStringError_ContentOnOpeningLine:
|
||||
syntax_error(pos, "A multi-line string literal must begin on the line after the opening delimiter");
|
||||
break;
|
||||
case TripleStringError_ClosingNotOnOwnLine:
|
||||
syntax_error(pos, "The closing delimiter of a multi-line string literal must be on its own line");
|
||||
break;
|
||||
case TripleStringError_UnderIndented:
|
||||
syntax_error(pos, "This line is indented less than the closing delimiter of the multi-line string literal");
|
||||
break;
|
||||
case TripleStringError_IndentationMismatch:
|
||||
syntax_error(pos, "The indentation of this line does not match the closing delimiter of the multi-line string literal");
|
||||
break;
|
||||
case TripleStringError_InvalidEscape:
|
||||
syntax_error(pos, "Invalid escape sequence in string literal");
|
||||
break;
|
||||
default:
|
||||
syntax_error(pos, "Invalid multi-line string literal");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (!unquote_string(ast_allocator(f), &s, 0, s.text[0] == '`')) {
|
||||
syntax_error(token, "Invalid string literal");
|
||||
|
||||
145
src/string.cpp
145
src/string.cpp
@@ -1360,34 +1360,54 @@ gb_internal bool triple_string_deindent(gbAllocator a, String body, String *out)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Triple-quoted string literal, dispatching on the delimiter:
|
||||
// `"` -> escapes processed; multi-line form is Java-style de-indented
|
||||
// `\`` -> raw (no escapes); multi-line form is ALSO Java-style de-indented
|
||||
// (carriage returns normalized), embedded single/double backticks
|
||||
// are literal
|
||||
|
||||
enum TripleStringErrorKind {
|
||||
TripleStringError_None = 0,
|
||||
TripleStringError_InvalidLiteral, // malformed delimiters (shouldn't occur for a real token)
|
||||
TripleStringError_ContentOnOpeningLine, // text after the opening delimiter on its own line
|
||||
TripleStringError_ClosingNotOnOwnLine, // closing delimiter is not alone on its line
|
||||
TripleStringError_UnderIndented, // a content line is indented less than the closing delimiter
|
||||
TripleStringError_IndentationMismatch, // a line's indentation whitespace differs from the closing delimiter
|
||||
TripleStringError_InvalidEscape, // bad escape sequence (only reachable for `"""` strings)
|
||||
};
|
||||
|
||||
// `out_err` receives the failure reason and `out_err_offset` a byte offset into
|
||||
// the *token string* (i.e. into *s_ on entry) pointing at the offending
|
||||
// character, or -1 when no precise location is available. Both are only
|
||||
// meaningful when the function returns 0.
|
||||
//
|
||||
// 0 == failure
|
||||
// 1 == original memory
|
||||
// 2 == new allocation
|
||||
gb_internal i32 unquote_string_triple(gbAllocator a, String *s_, bool has_carriage_return=false) {
|
||||
gb_internal i32 unquote_string_triple(gbAllocator a, String *s_, bool has_carriage_return=false,
|
||||
TripleStringErrorKind *out_err=nullptr, isize *out_err_offset=nullptr) {
|
||||
if (out_err) *out_err = TripleStringError_None;
|
||||
if (out_err_offset) *out_err_offset = -1;
|
||||
|
||||
#define TRIPLE_FAIL(kind, off) do { \
|
||||
if (out_err) *out_err = (kind); \
|
||||
if (out_err_offset) *out_err_offset = (off); \
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
String s = *s_;
|
||||
isize n = s.len;
|
||||
if (n < 6) {
|
||||
return 0;
|
||||
TRIPLE_FAIL(TripleStringError_InvalidLiteral, 0);
|
||||
}
|
||||
u8 quote = s[0];
|
||||
if (quote != '"' && quote != '`') {
|
||||
return 0;
|
||||
TRIPLE_FAIL(TripleStringError_InvalidLiteral, 0);
|
||||
}
|
||||
if (!(s[0] == quote && s[1] == quote && s[2] == quote) ||
|
||||
!(s[n-1] == quote && s[n-2] == quote && s[n-3] == quote)) {
|
||||
return 0;
|
||||
TRIPLE_FAIL(TripleStringError_InvalidLiteral, 0);
|
||||
}
|
||||
|
||||
String body = make_string(s.text+3, s.len-6);
|
||||
bool raw = (quote == '`');
|
||||
String body = make_string(s.text+3, s.len-6); // body begins at token offset 3
|
||||
|
||||
// Single-line form: no indentation handling.
|
||||
// Single-line form: no indentation handling
|
||||
if (!string_contains_char(body, '\n')) {
|
||||
if (raw) {
|
||||
if (has_carriage_return) {
|
||||
@@ -1397,22 +1417,109 @@ gb_internal i32 unquote_string_triple(gbAllocator a, String *s_, bool has_carria
|
||||
*s_ = body;
|
||||
return 1;
|
||||
}
|
||||
return unquote_string_triple_content(a, s_, body, has_carriage_return, false);
|
||||
i32 r = unquote_string_triple_content(a, s_, body, has_carriage_return, false);
|
||||
if (r == 0) {
|
||||
TRIPLE_FAIL(TripleStringError_InvalidEscape, -1);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Multi-line form: Java-style de-indentation for both delimiters.
|
||||
String di = {};
|
||||
if (!triple_string_deindent(a, body, &di)) {
|
||||
return 0;
|
||||
// Opening delimiter's line must be whitespace only.
|
||||
isize first_nl = 0;
|
||||
while (body.text[first_nl] != '\n') {
|
||||
first_nl += 1;
|
||||
}
|
||||
for (isize i = 0; i < first_nl; i++) {
|
||||
u8 c = body.text[i];
|
||||
if (c != ' ' && c != '\t' && c != '\r') {
|
||||
TRIPLE_FAIL(TripleStringError_ContentOnOpeningLine, 3 + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Closing delimiter's line: whitespace after the final newline is the indent prefix.
|
||||
isize last_nl = body.len - 1;
|
||||
while (body.text[last_nl] != '\n') {
|
||||
last_nl -= 1;
|
||||
}
|
||||
String indent = make_string(body.text+last_nl+1, body.len-(last_nl+1));
|
||||
for (isize i = 0; i < indent.len; i++) {
|
||||
u8 c = indent.text[i];
|
||||
if (c != ' ' && c != '\t') {
|
||||
TRIPLE_FAIL(TripleStringError_ClosingNotOnOwnLine, 3 + (last_nl+1) + i);
|
||||
}
|
||||
}
|
||||
|
||||
String content = make_string(body.text+first_nl+1, last_nl-(first_nl+1));
|
||||
isize content_base = 3 + (first_nl+1); // token offset of the content start
|
||||
|
||||
u8 *dbuf = gb_alloc_array(a, u8, content.len+1);
|
||||
isize dlen = 0;
|
||||
isize li = 0;
|
||||
for (;;) {
|
||||
isize le = li;
|
||||
while (le < content.len && content.text[le] != '\n') {
|
||||
le += 1;
|
||||
}
|
||||
isize len = le - li;
|
||||
if (len > 0 && content.text[li+len-1] == '\r') {
|
||||
len -= 1; // normalize CRLF
|
||||
}
|
||||
|
||||
bool blank = true;
|
||||
for (isize k = 0; k < len; k++) {
|
||||
u8 c = content.text[li+k];
|
||||
if (c != ' ' && c != '\t') {
|
||||
blank = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!blank) {
|
||||
isize ws = 0;
|
||||
while (ws < len && (content.text[li+ws] == ' ' || content.text[li+ws] == '\t')) {
|
||||
ws += 1;
|
||||
}
|
||||
isize ws_end_off = content_base + li + ws;
|
||||
|
||||
for (isize k = 0; k < indent.len; k++) {
|
||||
if (k >= len || content.text[li+k] != indent.text[k]) {
|
||||
u8 lc = 0;
|
||||
if (k < len) {
|
||||
lc = content.text[li+k];
|
||||
}
|
||||
gb_free(a, dbuf);
|
||||
|
||||
if (k < len && (lc == ' ' || lc == '\t')) {
|
||||
TRIPLE_FAIL(TripleStringError_IndentationMismatch, ws_end_off);
|
||||
}
|
||||
TRIPLE_FAIL(TripleStringError_UnderIndented, ws_end_off);
|
||||
}
|
||||
}
|
||||
for (isize k = indent.len; k < len; k++) {
|
||||
dbuf[dlen++] = content.text[li+k];
|
||||
}
|
||||
}
|
||||
if (le >= content.len) {
|
||||
break;
|
||||
}
|
||||
dbuf[dlen++] = '\n';
|
||||
li = le + 1;
|
||||
}
|
||||
|
||||
String di = make_string(dbuf, dlen);
|
||||
if (raw) {
|
||||
// Raw: the de-indented content is used verbatim (no escape processing).
|
||||
*s_ = di;
|
||||
return 2;
|
||||
}
|
||||
// Processed: run escape sequences on the de-indented content.
|
||||
return unquote_string_triple_content(a, s_, di, false, true);
|
||||
i32 r = unquote_string_triple_content(a, s_, di, false, true);
|
||||
if (r == 0) {
|
||||
TRIPLE_FAIL(TripleStringError_InvalidEscape, -1);
|
||||
}
|
||||
return r;
|
||||
|
||||
#undef TRIPLE_FAIL
|
||||
}
|
||||
|
||||
|
||||
gb_internal bool string_is_valid_identifier(String str) {
|
||||
if (str.len <= 0) return false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user