feat: support existing io.print formatting
This commit is contained in:
+57
-8
@@ -118,19 +118,27 @@ parse_decl proc(state @mut State) ast.NodeId ! ParseError {
|
||||
if (next_is(state, .at)) {
|
||||
start :: try consume(state, .at)
|
||||
name :: try consume(state, .ident)
|
||||
_ = try consume(state, .colon)
|
||||
qualifier :: try consume(state, .ident)
|
||||
file_local := false
|
||||
valid := mem.eql(state.builder.source[usize(name.span.start)..usize(name.span.end)], "hide")
|
||||
if (!valid) return .unexpected_token
|
||||
end := name.span.end
|
||||
if (next_is(state, .colon)) {
|
||||
state.next_token += 1
|
||||
qualifier :: try consume(state, .ident)
|
||||
scope :: state.builder.source[usize(qualifier.span.start)..usize(qualifier.span.end)]
|
||||
file_local = mem.eql(scope, "file")
|
||||
valid = valid and (file_local or mem.eql(scope, "package"))
|
||||
end = qualifier.span.end
|
||||
}
|
||||
skip_newlines(state)
|
||||
declaration :: try parse_decl(state)
|
||||
if (
|
||||
!mem.eql(state.builder.source[usize(name.span.start)..usize(name.span.end)], "hide")
|
||||
or !mem.eql(state.builder.source[usize(qualifier.span.start)..usize(qualifier.span.end)], "file")
|
||||
) {
|
||||
span :: ast.Span{ start = start.span.start, end = qualifier.span.end, source = start.span.source }
|
||||
if (!valid) {
|
||||
span :: ast.Span{ start = start.span.start, end = end, source = start.span.source }
|
||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, span)
|
||||
return try ast.add_invalid_decl(state.builder, span, diagnostic)
|
||||
}
|
||||
ast.set_file_local(state.builder, declaration)
|
||||
if (file_local) ast.set_file_local(state.builder, declaration)
|
||||
else ast.set_package_local(state.builder, declaration)
|
||||
return declaration
|
||||
}
|
||||
if (
|
||||
@@ -494,6 +502,7 @@ parse_if_expr proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .if)
|
||||
condition :: try parse_guard(state)
|
||||
capture :: try parse_optional_capture(state)
|
||||
skip_newlines(state)
|
||||
then_value :: try parse_expr(state)
|
||||
skip_newlines(state)
|
||||
if (!next_is(state, .else) or next_n_is(state, 1, .colon)) return .unexpected_token
|
||||
@@ -1792,6 +1801,39 @@ brace_starts_block proc(state @State) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
decode_character proc(text []u8) ?u64 {
|
||||
if (text.len < 3) return null
|
||||
contents :: text[1..text.len - 1]
|
||||
if (contents.len == 2 and contents[0] == '\\') {
|
||||
return match contents[1] {
|
||||
'0': u64(0)
|
||||
'n': u64('\n')
|
||||
'r': u64('\r')
|
||||
't': u64('\t')
|
||||
'\\': u64('\\')
|
||||
'\'': u64('\'')
|
||||
else: null
|
||||
}
|
||||
}
|
||||
first :: contents[0]
|
||||
if (first < 128) return if (contents.len == 1) u64(first) else null
|
||||
width :: if (first >= 194 and first <= 223) usize(2)
|
||||
else if (first >= 224 and first <= 239) usize(3)
|
||||
else if (first >= 240 and first <= 244) usize(4)
|
||||
else usize(0)
|
||||
if (width == 0 or contents.len != width) return null
|
||||
mask :: if (width == 2) u8(31) else if (width == 3) u8(15) else u8(7)
|
||||
value := u64(first & mask)
|
||||
for (contents[1..]) |byte| {
|
||||
if (byte < 128 or byte > 191) return null
|
||||
value = value * 64 + u64(byte & 63)
|
||||
}
|
||||
if ((width == 3 and value < 2048) or (width == 4 and value < 65536)
|
||||
or (value >= 55296 and value <= 57343) or value > 1114111) return null
|
||||
return value
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
token :: current(state)
|
||||
@@ -1872,6 +1914,13 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
state.next_token += 1
|
||||
return try ast.add_literal(state.builder, .int, token.span)
|
||||
}
|
||||
.character: {
|
||||
state.next_token += 1
|
||||
text :: state.builder.source[usize(token.span.start)..usize(token.span.end)]
|
||||
if (decode_character(text)) |value| return try ast.add_integer(state.builder, token.span, value)
|
||||
diagnostic :: try diagnostics.add(state.diagnostics, .invalid_character_literal, token.span)
|
||||
return try ast.add_invalid_expr(state.builder, token.span, diagnostic)
|
||||
}
|
||||
.float: {
|
||||
state.next_token += 1
|
||||
return try ast.add_literal(state.builder, .float, token.span)
|
||||
|
||||
@@ -8,6 +8,102 @@ import "@source/lexer"
|
||||
import "@source/ast"
|
||||
import "@source/ast/renderer"
|
||||
|
||||
parses_characters_as_integer_literals_with_original_spans test {
|
||||
source :: "values :: ['a', '\\0', '\\n', '\\r', '\\t', '\\\\', '\\'', 'é', '😀']"
|
||||
expected [9][]u8 :: ["97", "0", "10", "13", "9", "92", "39", "233", "128512"]
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
builder := ast.init_builder(mem.c_allocator, "")
|
||||
defer ast.deinit_builder(&builder)
|
||||
_ = try ast.set_source(&builder, "other.hon", "")
|
||||
source_id :: try ast.set_source(&builder, "characters.hon", source)
|
||||
scan_state.source_id = source_id
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
|
||||
tree :: ast.view(&builder, root)
|
||||
values :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
|
||||
try testing.expect_equal(expected.len, ast.array_element_count(&tree, values))
|
||||
for (expected) |text, index| {
|
||||
value :: ast.array_element(&tree, values, index)
|
||||
try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, value))
|
||||
try testing.expect_equal(text, ast.literal_text(&tree, value))
|
||||
span :: ast.node_span(&tree, value)
|
||||
try testing.expect_equal(source_id, span.source)
|
||||
try testing.expect_equal(u8('\''), ast.source_text(&tree, span)[0])
|
||||
try testing.expect_equal(scan_state.tokens.items[3 + index * 2].span.start, span.start)
|
||||
try testing.expect_equal(scan_state.tokens.items[3 + index * 2].span.end, span.end)
|
||||
}
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
diagnoses_malformed_character_values_without_losing_sibling_declarations test {
|
||||
sources [7][]u8 :: [
|
||||
"bad :: ''\nnext :: 1",
|
||||
"bad :: 'ab'\nnext :: 1",
|
||||
"bad :: '\\x41'\nnext :: 1",
|
||||
"bad :: '\\q'\nnext :: 1",
|
||||
"bad :: '\xC0\xAF'\nnext :: 1",
|
||||
"bad :: '\xED\xA0\x80'\nnext :: 1",
|
||||
"bad :: '\xF4\x90\x80\x80'\nnext :: 1",
|
||||
]
|
||||
for (sources) |source| {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
||||
bad :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, bad))
|
||||
diagnostic :: diagnostics.get(&diagnostic_store, ast.invalid(&tree, bad).diagnostic)?
|
||||
try testing.expect_equal(diagnostics.Code.invalid_character_literal, diagnostic.code)
|
||||
try testing.expect_equal(scan_state.tokens.items[2].span.start, diagnostic.span.start)
|
||||
try testing.expect_equal(scan_state.tokens.items[2].span.end, diagnostic.span.end)
|
||||
try testing.expect_equal("next", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name))
|
||||
}
|
||||
}
|
||||
|
||||
parses_multiline_conditional_values_and_hide_scopes test {
|
||||
source ::
|
||||
`@hide
|
||||
`hidden :: if (true)
|
||||
` '0'
|
||||
`else if (false)
|
||||
` 'A'
|
||||
`else
|
||||
` 'a'
|
||||
`@hide:package scoped :: 2
|
||||
`@hide:file local :: 3
|
||||
`visible :: 4
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
try testing.expect_equal(4, ast.package_decl_count(&tree))
|
||||
for (usize(0)..4) |index| {
|
||||
declaration :: ast.package_decl(&tree, index)
|
||||
try testing.expect_equal(index < 2, ast.package_local(&tree, declaration))
|
||||
try testing.expect_equal(index == 2, ast.file_local(&tree, declaration))
|
||||
}
|
||||
conditional :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.match_expr, ast.expr_kind(&tree, conditional))
|
||||
}
|
||||
|
||||
parses_expression_precedence_through_tree_views test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
|
||||
Reference in New Issue
Block a user