feat: support compound updates and bitwise operators
This commit is contained in:
+19
-5
@@ -2,8 +2,8 @@
|
||||
#! * type ..... -> type_sum ['!' type]; type_sum -> type_atom {'|' type_atom}; type_atom includes '(' type ')', identifier {'.' member | '(' [expr {',' expr} [',']] ')' | '[' expr ']'}, '?' type_atom, ('@' | '*') ['mut'] type_atom, ['@'] 'proc' '(' parameters ')' type, '[' (expr | '_') ']' ['mut'] type_atom, '[' [';' expr] ']' ['mut'] type_atom, record/enum/union declarations, and ('distinct' | 'alias') type.
|
||||
#! * binding ... -> :: | :=
|
||||
#! * literal .. -> int | float | string | 'true' | 'false' | 'null'
|
||||
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
||||
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '&' | 'try') expr | expr ('^' | '?') | expr op expr | expr '(' [expr {',' expr} [',']] ')' | identifier '!' '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '[' [expr] '..' [expr] [';' expr] ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}' | expr 'catch' ['|' identifier '|'] (expr | block)
|
||||
#! * op ....... -> '+' | '-' | '*' | '/' | '&' | 'xor' | '|' | '<<' | '>>' | '<<|' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
||||
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '~' | '&' | 'try') expr | expr ('^' | '?') | expr op expr | expr '(' [expr {',' expr} [',']] ')' | identifier '!' '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '[' [expr] '..' [expr] [';' expr] ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}' | expr 'catch' ['|' identifier '|'] (expr | block)
|
||||
#! * decl ..... -> identifier [type] binding expr | identifier 'c_func' '(' parameters ')' type | ['test'] [identifier '::'] 'import' string
|
||||
#! * parameters -> [['$'] identifier {',' ['$'] identifier} type {',' ['$'] identifier {',' ['$'] identifier} type} [',']]; '$' marks each compile-time parameter independently.
|
||||
#! '$(' expr ')' is an explicit compile-time expression with primary-expression precedence.
|
||||
@@ -14,9 +14,11 @@
|
||||
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
||||
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
|
||||
#! enum members and tagged-union variants accept keyword names; member names do not enter lexical scopes.
|
||||
#! * stmt ..... -> decl | expr | block | ('defer' | 'errdefer' ['|' identifier '|']) arm | expr '=' expr | 'return' [expr] | 'yield' expr | 'break' | 'continue' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | ['inline'] 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
||||
#! braceless guards/iterables require parentheses unless they are calls, explicit comptime expressions or optional captures; else binds to the nearest if.
|
||||
#! precedence (tightest first): calls/indices/selections/dereference/unwrap, unary -/!/&/try higher than multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges/catch. type composition | binds tighter than fallible !; pointer/optional/array prefixes bind to type atoms. compound range endpoints require their own parentheses.
|
||||
#! * stmt ..... -> decl | expr | block | ('defer' | 'errdefer' ['|' identifier '|']) arm | expr assignment_op expr | 'return' [expr] | 'yield' expr | 'break' | 'continue' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | ['inline'] 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
||||
#! assignment_op is '=' or a contiguous compound: '+=', '-=', '*=', '/=', '&=', '|=', 'xor=', '<<=', '>>=', '<<|='.
|
||||
#! braceless guards/iterables require parentheses unless they are calls, explicit comptime expressions or optional captures; else binds to the nearest if. bitwise-OR headers require parentheses before captures.
|
||||
#! precedence (tightest first): calls/indices/selections/dereference/unwrap, unary -, !, ~, &, try higher than multiplication/division, addition/subtraction, shifts << / >> / <<|, bitwise &/xor/|, relational comparisons, equality, and, or, ranges/catch. type composition | binds tighter than fallible !; pointer/optional/array prefixes bind to type atoms. compound range endpoints require their own parentheses.
|
||||
#! compound assignments share their target with the canonical binary value's left operand; lowering evaluates the place and loads its old value once before the RHS.
|
||||
#! binary operators associate left; unary operators associate right.
|
||||
|
||||
import "@std/debug"
|
||||
@@ -118,6 +120,7 @@ Mutability :: enum {
|
||||
UnaryOperator :: enum {
|
||||
negate
|
||||
logical_not
|
||||
bit_not
|
||||
address_of
|
||||
dereference
|
||||
unwrap
|
||||
@@ -126,6 +129,8 @@ UnaryOperator :: enum {
|
||||
BinaryOperator :: enum {
|
||||
add, subtract
|
||||
multiply, divide
|
||||
bit_and, bit_or, bit_xor
|
||||
shift_left, shift_right, shift_left_saturating
|
||||
equal, not_equal
|
||||
less, less_equal
|
||||
greater, greater_equal
|
||||
@@ -214,6 +219,7 @@ NodeData :: union {
|
||||
@hide:file variadic_flag u8 :: 0b0100
|
||||
@hide:file opaque_flag u8 :: 0b1000
|
||||
@hide:file optional_match_flag u8 :: 0b00010000
|
||||
@hide:file compound_assignment_flag u8 :: 0b00100000
|
||||
|
||||
@hide:file
|
||||
Node :: struct {
|
||||
@@ -405,6 +411,7 @@ AssignmentView :: struct {
|
||||
span Span
|
||||
target NodeId
|
||||
value NodeId
|
||||
compound bool = false
|
||||
}
|
||||
|
||||
WhileView :: struct {
|
||||
@@ -1023,6 +1030,12 @@ add_assignment_stmt proc(
|
||||
}, span)
|
||||
}
|
||||
|
||||
set_assignment_compound proc(builder @mut Builder, id NodeId) void {
|
||||
node :: &builder.storage.nodes.items[usize(id)]
|
||||
debug.assert(node.kind == .stmt_assignment)
|
||||
node.flags = node.flags | compound_assignment_flag
|
||||
}
|
||||
|
||||
add_while_stmt proc(
|
||||
builder @mut Builder,
|
||||
span Span,
|
||||
@@ -1416,6 +1429,7 @@ assignment_stmt proc(tree @Tree, id NodeId) AssignmentView {
|
||||
span = get_span(&tree.storage, id),
|
||||
target = node.main.node_id,
|
||||
value = node.data0.node_id,
|
||||
compound = (node.flags & compound_assignment_flag) != 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,8 +94,17 @@ render_block proc(renderer @mut Renderer, tree @ast.Tree, block ast.NodeId, dept
|
||||
assignment :: ast.assignment_stmt(tree, statement)
|
||||
try append_text(renderer, "assignment ")
|
||||
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, assignment.target)))
|
||||
try append_text(renderer, " = ")
|
||||
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, assignment.value)))
|
||||
value := assignment.value
|
||||
if (assignment.compound) {
|
||||
binary :: ast.binary_expr(tree, value)
|
||||
try append_text(renderer, " ")
|
||||
try append_text(renderer, assignment_operator_text(binary.operator))
|
||||
try append_text(renderer, "= ")
|
||||
value = binary.right
|
||||
} else {
|
||||
try append_text(renderer, " = ")
|
||||
}
|
||||
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, value)))
|
||||
try append_text(renderer, "\n")
|
||||
}
|
||||
.while_stmt: {
|
||||
@@ -151,3 +160,20 @@ render_statement proc(
|
||||
append_text proc(renderer @mut Renderer, text []u8) void ! mem.AllocError {
|
||||
for (text) |byte| try arraylist.append(&renderer.bytes, byte)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
assignment_operator_text proc(operator ast.BinaryOperator) []u8 {
|
||||
return match operator {
|
||||
.add: "+"
|
||||
.subtract: "-"
|
||||
.multiply: "*"
|
||||
.divide: "/"
|
||||
.bit_and: "&"
|
||||
.bit_or: "|"
|
||||
.bit_xor: "xor"
|
||||
.shift_left: "<<"
|
||||
.shift_right: ">>"
|
||||
.shift_left_saturating: "<<|"
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -847,3 +847,50 @@ yielding_blocks_recompile_owned_constants_and_generated_types test {
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
}
|
||||
|
||||
compound_updates_and_type_composition_recompile_without_retaining_rewrites test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
modules [2]mut ir.Module := undefined
|
||||
{
|
||||
session := init(mem.c_allocator)
|
||||
defer deinit(&session)
|
||||
root :: try add_package(&session)
|
||||
bytes := try owned_text(
|
||||
`A :: enum { first }
|
||||
`B :: enum { second }
|
||||
`Errors :: A | B
|
||||
`Box proc() type {
|
||||
` counts [1]mut usize := [1]
|
||||
` counts[0] <<= 1
|
||||
` return struct { values [counts[0]]i16 }
|
||||
`}
|
||||
`compute proc() u8 {
|
||||
` value u8 := 21
|
||||
` value <<= 1
|
||||
` return value
|
||||
`}
|
||||
`main proc() i16 {
|
||||
` box Box() :: { values = [40, 2] }
|
||||
` answer i16 := i16($(compute()))
|
||||
` answer xor= box.values[0] + box.values[1]
|
||||
` return answer + 42
|
||||
`}
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&bytes)
|
||||
_ = try add_source(&session, root, "operators.hon", &bytes, &diagnostic_store)
|
||||
modules[0] = try compile(&session, root, &diagnostic_store)
|
||||
errdefer ir.deinit(&modules[0])
|
||||
modules[1] = try compile(&session, root, &diagnostic_store)
|
||||
}
|
||||
defer ir.deinit(&modules[0])
|
||||
defer ir.deinit(&modules[1])
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
for modules |module| {
|
||||
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
defer interpreter.deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ Code :: enum {
|
||||
missing_metadata_import
|
||||
invalid_metadata_abi
|
||||
invalid_undefined
|
||||
invalid_shift_count
|
||||
}
|
||||
|
||||
Diagnostic :: struct {
|
||||
@@ -179,6 +180,7 @@ messages :: $enummap.init(Code, []u8, {
|
||||
type_mismatch = "incompatible value type",
|
||||
invalid_operand = "operator requires compatible operand types",
|
||||
integer_division = "division requires float operands",
|
||||
invalid_shift_count = "shift count must be smaller than the integer bit width",
|
||||
not_callable = "value is not callable",
|
||||
argument_count_mismatch = "argument count does not match the function signature",
|
||||
cannot_infer_type = "cannot infer a type without a type anchor",
|
||||
|
||||
@@ -587,6 +587,29 @@ execute proc(
|
||||
operand :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
value = ir.Value{ type = .bool, data = ir.ValueData{ boolean = !operand.data.boolean } }
|
||||
}
|
||||
.bit_not: {
|
||||
operand :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
mask :: maxval!(u64) >> u64(64 - ir.integer_bits(instruction.type))
|
||||
value = integer_from_bits(instruction.type, operand.data.unsigned xor mask)
|
||||
}
|
||||
.bit_and, .bit_or, .bit_xor: {
|
||||
left :: frames.items[frame_index].values[usize(instruction.left)].data.unsigned
|
||||
right :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
|
||||
bits u64 :: match instruction.op {
|
||||
.bit_and: left & right
|
||||
.bit_or: left | right
|
||||
.bit_xor: left xor right
|
||||
else: unreachable
|
||||
}
|
||||
value = integer_from_bits(instruction.type, bits)
|
||||
}
|
||||
.shift_left, .shift_right, .shift_left_saturating: {
|
||||
left :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
count :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
|
||||
if (instruction.op != .shift_left_saturating and count >= u64(ir.integer_bits(instruction.type)))
|
||||
return try runtime_trap(diagnostic_store, .invalid_shift_count, instruction.span)
|
||||
value = shift_integer(instruction.op, left, count)
|
||||
}
|
||||
.add, .subtract, .multiply, .divide: {
|
||||
left :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
right :: frames.items[frame_index].values[usize(instruction.right)]
|
||||
@@ -901,6 +924,46 @@ runtime_trap proc(
|
||||
return Outcome{ trap = try diagnostics.add(diagnostic_store, code, span) }
|
||||
}
|
||||
|
||||
@hide:file
|
||||
integer_from_bits proc(type ir.Type, bits u64) ir.Value {
|
||||
data := ir.ValueData{ unsigned = bits }
|
||||
match type {
|
||||
.i8: data = ir.ValueData{ integer = i64(ptrcast!(@i8, &bits)^) }
|
||||
.i16: data = ir.ValueData{ integer = i64(ptrcast!(@i16, &bits)^) }
|
||||
.i32: data = ir.ValueData{ integer = i64(ptrcast!(@i32, &bits)^) }
|
||||
.int, .isize: data = ir.ValueData{ integer = ptrcast!(@i64, &bits)^ }
|
||||
.u8: data = ir.ValueData{ unsigned = u64(ptrcast!(@u8, &bits)^) }
|
||||
.u16: data = ir.ValueData{ unsigned = u64(ptrcast!(@u16, &bits)^) }
|
||||
.u32: data = ir.ValueData{ unsigned = u64(ptrcast!(@u32, &bits)^) }
|
||||
.u64, .usize: {}
|
||||
else: unreachable
|
||||
}
|
||||
return ir.Value{ type = type, data = data }
|
||||
}
|
||||
|
||||
@hide:file
|
||||
shift_integer proc(op ir.Op, left ir.Value, count u64) ir.Value {
|
||||
width :: u64(ir.integer_bits(left.type))
|
||||
mask :: maxval!(u64) >> (64 - width)
|
||||
bits :: left.data.unsigned & mask
|
||||
signed :: !ir.unsigned_integer(left.type)
|
||||
negative :: signed and left.data.integer < 0
|
||||
if (op == .shift_left_saturating) {
|
||||
if (bits == 0) return left
|
||||
limit :: if (negative) 1 + (mask >> 1) else ir.integer_max(left.type)
|
||||
magnitude :: if (negative) 1 + (bits xor mask) else bits
|
||||
if (count >= width) return integer_from_bits(left.type, limit)
|
||||
if (magnitude > (limit >> count)) return integer_from_bits(left.type, limit)
|
||||
}
|
||||
if (op == .shift_right) {
|
||||
shifted := bits >> count
|
||||
if (negative) shifted = shifted | (mask xor (mask >> count))
|
||||
return integer_from_bits(left.type, shifted)
|
||||
}
|
||||
# discard high bits before shifting so the bootstrap's checked arithmetic cannot overflow.
|
||||
return integer_from_bits(left.type, (bits & (mask >> count)) << count)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
checked_integer proc(op ir.Op, left i64, right i64) ?i64 {
|
||||
minimum :: minval!(i64)
|
||||
|
||||
+8
-12
@@ -155,22 +155,18 @@ Op :: enum {
|
||||
slice_check
|
||||
sentinel_check
|
||||
optional_check
|
||||
memory_copy
|
||||
memory_set
|
||||
memory_copy, memory_set
|
||||
negate
|
||||
convert
|
||||
logical_not
|
||||
add
|
||||
subtract
|
||||
multiply
|
||||
divide
|
||||
bit_not, bit_and, bit_or, bit_xor
|
||||
shift_left, shift_right, shift_left_saturating
|
||||
add, subtract
|
||||
multiply, divide
|
||||
call
|
||||
equal
|
||||
not_equal
|
||||
less
|
||||
less_equal
|
||||
greater
|
||||
greater_equal
|
||||
equal, not_equal
|
||||
less, less_equal
|
||||
greater, greater_equal
|
||||
}
|
||||
|
||||
Instruction :: struct {
|
||||
|
||||
+34
-3
@@ -41,6 +41,7 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
||||
{ "false", .false },
|
||||
{ "and", .and },
|
||||
{ "or", .or },
|
||||
{ "xor", .xor },
|
||||
{ "mut", .mut },
|
||||
{ "struct", .struct },
|
||||
{ "c_struct", .c_struct },
|
||||
@@ -107,12 +108,16 @@ scan proc(
|
||||
if is_alpha(char) or char == '_' {
|
||||
start :: cursor
|
||||
result :: try scan_ident(start, program, pool)
|
||||
compound :: result.kind == .xor and result.end < program.len and program[result.end] == '='
|
||||
extra :: if (compound) usize(1) else 0
|
||||
end :: result.end + extra
|
||||
kind :: if (compound) TokenKind.xor_equal else result.kind
|
||||
try add_token(state, Token{
|
||||
span = make_span(source, start, result.end),
|
||||
kind = result.kind,
|
||||
span = make_span(source, start, end),
|
||||
kind = kind,
|
||||
data = TokenData{ str_id = result.str_id },
|
||||
})
|
||||
cursor = result.end
|
||||
cursor = end
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -281,12 +286,37 @@ scan proc(
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
(char == '<' or char == '>') and cursor + 1 < program.len
|
||||
and program[cursor + 1] == char
|
||||
) {
|
||||
saturating :: char == '<' and cursor + 2 < program.len and program[cursor + 2] == '|'
|
||||
width :: if (saturating) usize(3) else 2
|
||||
compound :: cursor + width < program.len and program[cursor + width] == '='
|
||||
kind TokenKind := .shift_right
|
||||
if (saturating) {
|
||||
kind = if (compound) .shift_left_saturating_equal else .shift_left_saturating
|
||||
} else if (char == '<') {
|
||||
kind = if (compound) .shift_left_equal else .shift_left
|
||||
} else if (compound) kind = .shift_right_equal
|
||||
extra :: if (compound) usize(1) else 0
|
||||
end :: cursor + width + extra
|
||||
try add_token(state, Token{
|
||||
span = make_span(source, cursor, end),
|
||||
kind = kind,
|
||||
})
|
||||
cursor = end
|
||||
continue
|
||||
}
|
||||
|
||||
if (cursor + 1 < program.len and program[cursor + 1] == '=') {
|
||||
comparison ?TokenKind :: match char {
|
||||
'=': .equal_equal
|
||||
'!': .not_equal
|
||||
'<': .less_equal
|
||||
'>': .greater_equal
|
||||
'&': .ampersand_equal
|
||||
'|': .pipe_equal
|
||||
else: null
|
||||
}
|
||||
if (comparison) |value| {
|
||||
@@ -302,6 +332,7 @@ scan proc(
|
||||
kind ?TokenKind :: match char {
|
||||
'=': .equal
|
||||
'!': .bang
|
||||
'~': .tilde
|
||||
'<': .less
|
||||
'>': .greater
|
||||
',': .comma
|
||||
|
||||
@@ -11,7 +11,7 @@ reports_invalid_input_and_keeps_scanning test {
|
||||
state := init(mem.c_allocator)
|
||||
defer deinit(&state)
|
||||
|
||||
try scan(&state, "if name # comment\n~1. 2 3.5 \"hi\"", &pool, &diagnostic_store)
|
||||
try scan(&state, "if name # comment\n%1. 2 3.5 \"hi\"", &pool, &diagnostic_store)
|
||||
|
||||
try testing.expect_equal(9, state.tokens.items.len)
|
||||
try testing.expect_equal(TokenKind.if, state.tokens.items[0].kind)
|
||||
@@ -266,7 +266,7 @@ scans_import_keywords_and_preserves_source_in_tokens_and_diagnostics test {
|
||||
state := init(mem.c_allocator)
|
||||
defer deinit(&state)
|
||||
state.source_id = diagnostics.SourceId(7)
|
||||
try scan(&state, "import test ~\n1.\n\"unfinished", &pool, &diagnostic_store)
|
||||
try scan(&state, "import test %\n1.\n\"unfinished", &pool, &diagnostic_store)
|
||||
try testing.expect_equal(TokenKind.import, state.tokens.items[0].kind)
|
||||
try testing.expect_equal(TokenKind.test, state.tokens.items[1].kind)
|
||||
for (state.tokens.items) |token| try testing.expect_equal(state.source_id, token.span.source)
|
||||
@@ -290,3 +290,29 @@ reserves_yield_without_splitting_identifiers test {
|
||||
try testing.expect_equal("yield", strpool.get_str(&pool, state.tokens.items[0].data.str_id)?)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
scans_bitwise_operators_and_contiguous_compounds test {
|
||||
pool := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&pool)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
state := init(mem.c_allocator)
|
||||
defer deinit(&state)
|
||||
source :: "~ & | xor << >> <<| += -= *= /= &= |= xor= <<= >>= <<|= xor_flag xor = << |= <<| ="
|
||||
try scan(&state, source, &pool, &diagnostic_store)
|
||||
expected [25]TokenKind :: [
|
||||
.tilde, .ampersand, .pipe, .xor, .shift_left, .shift_right, .shift_left_saturating,
|
||||
.plus_equal, .minus_equal, .star_equal, .slash_equal,
|
||||
.ampersand_equal, .pipe_equal, .xor_equal,
|
||||
.shift_left_equal, .shift_right_equal, .shift_left_saturating_equal,
|
||||
.ident, .xor, .equal, .shift_left, .pipe_equal, .shift_left_saturating, .equal, .eof,
|
||||
]
|
||||
try testing.expect_equal(expected.len, state.tokens.items.len)
|
||||
for (expected) |kind, index| try testing.expect_equal(kind, state.tokens.items[index].kind)
|
||||
xor_span :: state.tokens.items[13].span
|
||||
try testing.expect_equal("xor=", source[usize(xor_span.start)..usize(xor_span.end)])
|
||||
saturating_span :: state.tokens.items[16].span
|
||||
try testing.expect_equal("<<|=", source[usize(saturating_span.start)..usize(saturating_span.end)])
|
||||
try testing.expect_equal("xor_flag", strpool.get_str(&pool, state.tokens.items[17].data.str_id)?)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ TokenKind :: enum {
|
||||
defer, errdefer
|
||||
true, false
|
||||
and, or
|
||||
xor
|
||||
struct, c_struct
|
||||
opaque
|
||||
enum, union
|
||||
@@ -56,6 +57,10 @@ TokenKind :: enum {
|
||||
plus, minus
|
||||
star, slash
|
||||
|
||||
# bitwise
|
||||
tilde
|
||||
shift_left, shift_right, shift_left_saturating
|
||||
|
||||
range, range_inclusive, ellipsis
|
||||
at, ampersand, caret, dollar
|
||||
question, semicolon
|
||||
@@ -67,6 +72,8 @@ TokenKind :: enum {
|
||||
# assignment & arithmetic
|
||||
plus_equal, minus_equal
|
||||
star_equal, slash_equal
|
||||
ampersand_equal, pipe_equal, xor_equal
|
||||
shift_left_equal, shift_right_equal, shift_left_saturating_equal
|
||||
|
||||
# delimiters
|
||||
open_paren, close_paren
|
||||
|
||||
@@ -304,6 +304,22 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
operand :: try load(state, instruction.left)
|
||||
try io.print(" %r{} = xor i1 %r{}, true\n", output, {result, operand})
|
||||
}
|
||||
.bit_not: {
|
||||
operand :: try load(state, instruction.left)
|
||||
try io.print(" %r{} = xor i{} %r{}, -1\n", output, {result, ir.integer_bits(instruction.type), operand})
|
||||
}
|
||||
.bit_and, .bit_or, .bit_xor: {
|
||||
left :: try load(state, instruction.left)
|
||||
right :: try load(state, instruction.right)
|
||||
opcode []u8 :: match instruction.op {
|
||||
.bit_and: "and"
|
||||
.bit_or: "or"
|
||||
.bit_xor: "xor"
|
||||
else: unreachable
|
||||
}
|
||||
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, ir.integer_bits(instruction.type), left, right})
|
||||
}
|
||||
.shift_left, .shift_right, .shift_left_saturating: result = try emit_shift(state, instruction)
|
||||
.add, .subtract, .multiply, .divide: {
|
||||
left :: try load(state, instruction.left)
|
||||
right :: try load(state, instruction.right)
|
||||
@@ -388,6 +404,58 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
try store(state, instruction.destination, result)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
emit_shift proc(state @mut State, instruction ir.Instruction) usize ! io.WriteError {
|
||||
output :: state.output
|
||||
left :: try load(state, instruction.left)
|
||||
right :: try load(state, instruction.right)
|
||||
bits :: ir.integer_bits(instruction.type)
|
||||
count_bits :: ir.integer_bits(state.values[usize(instruction.right)].kind)
|
||||
invalid :: fresh(state)
|
||||
try io.print(" %r{} = icmp uge i{} %r{}, {}\n", output, {invalid, count_bits, right, bits})
|
||||
count := right
|
||||
if (instruction.op == .shift_left_saturating) {
|
||||
# keep every evaluated LLVM shift in range, including the discarded saturation path.
|
||||
count = fresh(state)
|
||||
try io.print(" %r{} = select i1 %r{}, i{} 0, i{} %r{}\n", output, {count, invalid, count_bits, count_bits, right})
|
||||
} else {
|
||||
try check_failure(state, invalid, .invalid_shift_count, instruction.span)
|
||||
}
|
||||
if (count_bits != bits) {
|
||||
converted :: fresh(state)
|
||||
conversion []u8 :: if (count_bits < bits) "zext" else "trunc"
|
||||
try io.print(" %r{} = {s} i{} %r{} to i{}\n", output, {converted, conversion, count_bits, count, bits})
|
||||
count = converted
|
||||
}
|
||||
shifted :: fresh(state)
|
||||
signed :: !ir.unsigned_integer(instruction.type)
|
||||
right_opcode []u8 :: if (signed) "ashr" else "lshr"
|
||||
opcode []u8 :: if (instruction.op == .shift_right) right_opcode else "shl"
|
||||
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {shifted, opcode, bits, left, count})
|
||||
if (instruction.op != .shift_left_saturating) return shifted
|
||||
restored :: fresh(state)
|
||||
lost :: fresh(state)
|
||||
overflow :: fresh(state)
|
||||
nonzero :: fresh(state)
|
||||
saturate :: fresh(state)
|
||||
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {restored, right_opcode, bits, shifted, count})
|
||||
try io.print(" %r{} = icmp ne i{} %r{}, %r{}\n", output, {lost, bits, restored, left})
|
||||
try io.print(" %r{} = or i1 %r{}, %r{}\n", output, {overflow, invalid, lost})
|
||||
try io.print(" %r{} = icmp ne i{} %r{}, 0\n", output, {nonzero, bits, left})
|
||||
try io.print(" %r{} = and i1 %r{}, %r{}\n", output, {saturate, overflow, nonzero})
|
||||
limit :: fresh(state)
|
||||
if (signed) {
|
||||
negative :: fresh(state)
|
||||
try io.print(" %r{} = icmp slt i{} %r{}, 0\n", output, {negative, bits, left})
|
||||
try io.print(" %r{} = select i1 %r{}, i{} {}, i{} {}\n", output, {limit, negative, bits, ir.integer_min(instruction.type), bits, ir.integer_max(instruction.type)})
|
||||
} else {
|
||||
try io.print(" %r{} = or i{} 0, {}\n", output, {limit, bits, ir.integer_max(instruction.type)})
|
||||
}
|
||||
result :: fresh(state)
|
||||
try io.print(" %r{} = select i1 %r{}, i{} %r{}, i{} %r{}\n", output, {result, saturate, bits, limit, bits, shifted})
|
||||
return result
|
||||
}
|
||||
|
||||
#! compares the complete sentinel value, including nested aggregate fields.
|
||||
@hide:file
|
||||
sentinel_difference proc(state @mut State, type Type, actual, expected usize) usize ! io.WriteError {
|
||||
|
||||
@@ -64,10 +64,11 @@ emit proc(
|
||||
try bytes_constant(output, enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?, false)
|
||||
try io.write_all(output, "\n")
|
||||
}
|
||||
memory_codes [7]diagnostics.Code :: [
|
||||
memory_codes [_]diagnostics.Code :: [
|
||||
.null_unwrap, .invalid_slice_bounds, .sentinel_mismatch,
|
||||
.overlapping_memory_copy, .unsupported_foreign_call, .invalid_pointer_access,
|
||||
.division_by_zero,
|
||||
.invalid_shift_count,
|
||||
]
|
||||
for (memory_codes) |code| {
|
||||
try io.print("@memory_{s} = private constant ", output, {memory_symbol(code)})
|
||||
@@ -504,6 +505,7 @@ memory_symbol proc(code diagnostics.Code) []u8 {
|
||||
.unsupported_foreign_call: "unsupported_foreign_call"
|
||||
.invalid_pointer_access: "invalid_pointer_access"
|
||||
.division_by_zero: "division_by_zero"
|
||||
.invalid_shift_count: "invalid_shift_count"
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
+55
-13
@@ -927,6 +927,36 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
}
|
||||
.assignment_stmt: {
|
||||
assignment :: ast.assignment_stmt(tree, id)
|
||||
if (assignment.compound) {
|
||||
if (trap_type(state, id)) return
|
||||
expression :: ast.binary_expr(tree, assignment.value)
|
||||
address :: try lower_place(state, assignment.target)
|
||||
if (state.terminated) return
|
||||
checked :: typecheck.type_of(state.checked, assignment.target)
|
||||
type :: lower_type(state, checked)
|
||||
old :: try emit(state, ir.Instruction{
|
||||
op = .load_address, type = type, left = address,
|
||||
array_type = try array_shape(state, checked), span = ast.node_span(tree, assignment.target),
|
||||
})
|
||||
right :: try lower_expression(state, expression.right)
|
||||
if (state.terminated) return
|
||||
value :: if (expression.operator == .add and type == .pointer) {
|
||||
pointer :: typecheck.pointer_type(state.checked, checked.pointer_type)
|
||||
yield try emit(state, ir.Instruction{
|
||||
op = .pointer_offset, type = type, left = old, right = right,
|
||||
pointee = lower_type(state, pointer.element),
|
||||
array_type = try array_shape(state, pointer.element), span = ast.node_span(tree, id),
|
||||
})
|
||||
} else try emit(state, ir.Instruction{
|
||||
op = binary_op(expression.operator), type = type, left = old, right = right,
|
||||
span = ast.node_span(tree, id),
|
||||
})
|
||||
_ = try emit(state, ir.Instruction{
|
||||
op = .store_address, type = .void, left = address, right = value,
|
||||
span = ast.node_span(tree, assignment.target),
|
||||
})
|
||||
return
|
||||
}
|
||||
if (
|
||||
ast.expr_kind(tree, assignment.target) == .index
|
||||
or ast.expr_kind(tree, assignment.target) == .unary
|
||||
@@ -1016,6 +1046,29 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
binary_op proc(operator ast.BinaryOperator) ir.Op {
|
||||
return match operator {
|
||||
.add: .add
|
||||
.subtract: .subtract
|
||||
.multiply: .multiply
|
||||
.divide: .divide
|
||||
.bit_and: .bit_and
|
||||
.bit_or: .bit_or
|
||||
.bit_xor: .bit_xor
|
||||
.shift_left: .shift_left
|
||||
.shift_right: .shift_right
|
||||
.shift_left_saturating: .shift_left_saturating
|
||||
.equal: .equal
|
||||
.not_equal: .not_equal
|
||||
.less: .less
|
||||
.less_equal: .less_equal
|
||||
.greater: .greater
|
||||
.greater_equal: .greater_equal
|
||||
.logical_and, .logical_or: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
expression :: ast.binary_expr(state.tree, id)
|
||||
@@ -2199,6 +2252,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
op ir.Op :: match expression.operator {
|
||||
.negate: .negate
|
||||
.logical_not: .logical_not
|
||||
.bit_not: .bit_not
|
||||
.address_of, .dereference, .unwrap: unreachable
|
||||
}
|
||||
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
|
||||
@@ -2231,19 +2285,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
array_type = try array_shape(state, pointer.element), span = span,
|
||||
})
|
||||
}
|
||||
op ir.Op :: match expression.operator {
|
||||
.add: .add
|
||||
.subtract: .subtract
|
||||
.multiply: .multiply
|
||||
.divide: .divide
|
||||
.equal: .equal
|
||||
.not_equal: .not_equal
|
||||
.less: .less
|
||||
.less_equal: .less_equal
|
||||
.greater: .greater
|
||||
.greater_equal: .greater_equal
|
||||
.logical_and, .logical_or: unreachable
|
||||
}
|
||||
op :: binary_op(expression.operator)
|
||||
return try emit(state, ir.Instruction{ op = op, type = type, left = left, right = right, span = span })
|
||||
}
|
||||
.call: {
|
||||
|
||||
+95
-33
@@ -30,7 +30,7 @@ State :: struct {
|
||||
next_token usize
|
||||
paren_depth usize = 0
|
||||
allow_record_literals bool = true
|
||||
allow_sum_types bool = true
|
||||
allow_bit_or bool = true
|
||||
match_arm_body bool = false
|
||||
}
|
||||
|
||||
@@ -254,16 +254,16 @@ parse_block proc(state @mut State) ast.NodeId ! ParseError {
|
||||
open :: try consume(state, .open_curly)
|
||||
previous_depth :: state.paren_depth
|
||||
previous_record_literals :: state.allow_record_literals
|
||||
previous_sum_types :: state.allow_sum_types
|
||||
previous_bit_or :: state.allow_bit_or
|
||||
previous_match_arm_body :: state.match_arm_body
|
||||
state.paren_depth = 0
|
||||
state.allow_record_literals = true
|
||||
state.allow_sum_types = true
|
||||
state.allow_bit_or = true
|
||||
state.match_arm_body = false
|
||||
defer {
|
||||
state.paren_depth = previous_depth
|
||||
state.allow_record_literals = previous_record_literals
|
||||
state.allow_sum_types = previous_sum_types
|
||||
state.allow_bit_or = previous_bit_or
|
||||
state.match_arm_body = previous_match_arm_body
|
||||
}
|
||||
statements arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
@@ -325,10 +325,10 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
.break, .continue: return try parse_loop_control(state, allow_else)
|
||||
.defer, .errdefer: return try parse_cleanup(state, allow_else)
|
||||
.open_curly: return try ast.add_block_stmt(state.builder, try parse_block(state))
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .struct, .c_struct, .opaque, .c_func,
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .tilde, .open_paren, .open_bracket, .struct, .c_struct, .opaque, .c_func,
|
||||
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star, .question, .null, .undefined, .try, .dollar: {
|
||||
expression :: try parse_expr(state)
|
||||
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
|
||||
if (is_assignment_operator(current(state).kind)) return try parse_assignment(state, expression, allow_else)
|
||||
return try ast.add_expression_stmt(state.builder, expression)
|
||||
}
|
||||
else: return .unexpected_token
|
||||
@@ -431,8 +431,9 @@ parse_loop_control proc(state @mut State, allow_else bool) ast.NodeId ! ParseErr
|
||||
|
||||
@hide:file
|
||||
parse_assignment proc(state @mut State, target ast.NodeId, allow_else bool) ast.NodeId ! ParseError {
|
||||
_ = try consume(state, .equal)
|
||||
value :: try parse_expr(state)
|
||||
operator :: assignment_operator(current(state).kind)
|
||||
state.next_token += 1
|
||||
value := try parse_expr(state)
|
||||
if (
|
||||
!next_is(state, .newline)
|
||||
and !next_is(state, .close_curly)
|
||||
@@ -440,17 +441,15 @@ parse_assignment proc(state @mut State, target ast.NodeId, allow_else bool) ast.
|
||||
and !(allow_else and next_is(state, .else))
|
||||
and !(state.match_arm_body and next_is(state, .comma))
|
||||
) return .unexpected_token
|
||||
end :: state.tokens[state.next_token - 1].span.end
|
||||
return try ast.add_assignment_stmt(
|
||||
state.builder,
|
||||
ast.Span{
|
||||
start = ast.builder_span(state.builder, target).start,
|
||||
end = end,
|
||||
source = state.builder.source_id,
|
||||
},
|
||||
target,
|
||||
value,
|
||||
)
|
||||
span :: ast.Span{
|
||||
start = ast.builder_span(state.builder, target).start,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
source = state.builder.source_id,
|
||||
}
|
||||
if (operator) |binary| value = try ast.add_binary(state.builder, span, binary, target, value)
|
||||
id :: try ast.add_assignment_stmt(state.builder, span, target, value)
|
||||
if (operator != null) ast.set_assignment_compound(state.builder, id)
|
||||
return id
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -657,13 +656,13 @@ parse_for proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
@hide:file
|
||||
parse_guard_expr proc(state @mut State) ast.NodeId ! ParseError {
|
||||
previous :: state.allow_record_literals
|
||||
previous_sum_types :: state.allow_sum_types
|
||||
previous_bit_or :: state.allow_bit_or
|
||||
state.allow_record_literals = false
|
||||
# capture pipes stay boundaries even when an index/call permits record literals.
|
||||
state.allow_sum_types = false
|
||||
# top-level header pipes delimit captures; nested expressions may use bitwise OR.
|
||||
state.allow_bit_or = false
|
||||
defer {
|
||||
state.allow_record_literals = previous
|
||||
state.allow_sum_types = previous_sum_types
|
||||
state.allow_bit_or = previous_bit_or
|
||||
}
|
||||
return try parse_expr(state)
|
||||
}
|
||||
@@ -689,7 +688,7 @@ parse_guard proc(state @mut State) ast.NodeId ! ParseError {
|
||||
and !next_is(state, .for) and !next_is(state, .inline)
|
||||
and !next_is(state, .break) and !next_is(state, .continue)
|
||||
) {
|
||||
if (next_is(state, .ident) and next_n_is(state, 1, .equal)) break
|
||||
if (next_is(state, .ident) and is_assignment_operator(state.tokens[state.next_token + 1].kind)) break
|
||||
state.next_token += 1
|
||||
}
|
||||
}
|
||||
@@ -952,7 +951,7 @@ starts_type proc(kind lexer.TokenKind) bool {
|
||||
member_name proc(kind lexer.TokenKind) bool {
|
||||
return match kind {
|
||||
.ident, .if, .else, .for, .inline, .while, .proc, .return, .yield, .break, .continue,
|
||||
.true, .false, .and, .or, .mut, .struct, .c_struct, .opaque, .enum, .union, .distinct,
|
||||
.true, .false, .and, .or, .xor, .mut, .struct, .c_struct, .opaque, .enum, .union, .distinct,
|
||||
.alias, .match, .import, .test, .c_func, .null, .undefined, .try, .catch, .defer, .errdefer: true
|
||||
else: false
|
||||
}
|
||||
@@ -1193,7 +1192,7 @@ parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
|
||||
body = try parse_arm(state, false)
|
||||
} else {
|
||||
value :: try parse_expr(state)
|
||||
if next_is(state, .equal) {
|
||||
if (is_assignment_operator(current(state).kind)) {
|
||||
statement :: try parse_assignment(state, value, false)
|
||||
statements [1]ast.NodeId :: [statement]
|
||||
body = try ast.add_block(state.builder, ast.builder_span(state.builder, statement), statements[..])
|
||||
@@ -1391,6 +1390,7 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
}
|
||||
operator ast.UnaryOperator :: match token.kind {
|
||||
.bang: .logical_not
|
||||
.tilde: .bit_not
|
||||
.ampersand: .address_of
|
||||
else: .negate
|
||||
}
|
||||
@@ -1477,11 +1477,10 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
left_grouped = false
|
||||
continue
|
||||
}
|
||||
if (token.kind == .bang or (token.kind == .pipe and state.allow_sum_types)) {
|
||||
power :: if (token.kind == .bang) BindingPowerScalar(1) else BindingPowerScalar(2)
|
||||
if (power < min_bp) break
|
||||
if (token.kind == .bang) {
|
||||
if (BindingPowerScalar(1) < min_bp) break
|
||||
state.next_token += 1
|
||||
right :: try parse_expr_bp(state, BindingPowerScalar(u32(power) + 1))
|
||||
right :: try parse_type(state)
|
||||
left = try ast.add_binary_type(state.builder, ast.Span{
|
||||
start = state.tokens[expression_start].span.start,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
@@ -1509,6 +1508,7 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
left_grouped = false
|
||||
continue
|
||||
}
|
||||
if (token.kind == .pipe and !state.allow_bit_or) break
|
||||
if (infix_binding_power(token.kind)) |power| {
|
||||
if (power.left < min_bp) break
|
||||
state.next_token += 1
|
||||
@@ -1553,6 +1553,9 @@ validate_range_bound proc(
|
||||
parse_call proc(state @mut State, callee ast.NodeId, start u32, builtin bool) ast.NodeId ! ParseError {
|
||||
_ = try consume(state, .open_paren)
|
||||
previous :: state.allow_record_literals
|
||||
previous_bit_or :: state.allow_bit_or
|
||||
state.allow_bit_or = true
|
||||
defer state.allow_bit_or = previous_bit_or
|
||||
state.allow_record_literals = true
|
||||
defer state.allow_record_literals = previous
|
||||
state.paren_depth += 1
|
||||
@@ -1591,6 +1594,9 @@ parse_call proc(state @mut State, callee ast.NodeId, start u32, builtin bool) as
|
||||
parse_index proc(state @mut State, base ast.NodeId, start u32) ast.NodeId ! ParseError {
|
||||
_ = try consume(state, .open_bracket)
|
||||
previous :: state.allow_record_literals
|
||||
previous_bit_or :: state.allow_bit_or
|
||||
state.allow_bit_or = previous_bit_or or index_has_close(state)
|
||||
defer state.allow_bit_or = previous_bit_or
|
||||
state.allow_record_literals = true
|
||||
defer state.allow_record_literals = previous
|
||||
first ?ast.NodeId := null
|
||||
@@ -1621,6 +1627,22 @@ parse_index proc(state @mut State, base ast.NodeId, start u32) ast.NodeId ! Pars
|
||||
}, base, first?)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
index_has_close proc(state @State) bool {
|
||||
# keep malformed guard indices from consuming a capture list and its body.
|
||||
depth := usize(1)
|
||||
for (state.next_token)..(state.tokens.len) |index| {
|
||||
kind :: state.tokens[index].kind
|
||||
if (kind == .eof or (kind == .newline and depth == 1)) return false
|
||||
if (kind == .open_paren or kind == .open_bracket or kind == .open_curly) depth += 1
|
||||
if (kind == .close_paren or kind == .close_bracket or kind == .close_curly) {
|
||||
depth -= 1
|
||||
if (depth == 0) return kind == .close_bracket
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
array_starts_type proc(state @State) bool {
|
||||
depth usize := 0
|
||||
@@ -1650,6 +1672,9 @@ array_starts_type proc(state @State) bool {
|
||||
parse_array_literal proc(state @mut State) ast.NodeId ! ParseError {
|
||||
open :: try consume(state, .open_bracket)
|
||||
previous :: state.allow_record_literals
|
||||
previous_bit_or :: state.allow_bit_or
|
||||
state.allow_bit_or = true
|
||||
defer state.allow_bit_or = previous_bit_or
|
||||
state.allow_record_literals = true
|
||||
defer state.allow_record_literals = previous
|
||||
elements arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
@@ -1708,7 +1733,7 @@ pointer_binding_starts proc(state @State) bool {
|
||||
}
|
||||
if (depth != 0) continue
|
||||
if (kind == .double_colon or kind == .colon_equal) return true
|
||||
if (kind == .newline or kind == .eof or kind == .equal) return false
|
||||
if (kind == .newline or kind == .eof or is_assignment_operator(kind)) return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1744,6 +1769,7 @@ brace_starts_block proc(state @State) bool {
|
||||
and !(index + 1 < state.tokens.len and state.tokens[index + 1].kind == .equal)) return true
|
||||
if (depth == 1) {
|
||||
if (kind == .double_colon or kind == .colon_equal) return true
|
||||
if (kind != .equal and is_assignment_operator(kind)) return true
|
||||
previous :: if (index > first) state.tokens[index - 1].kind else lexer.TokenKind.open_curly
|
||||
if (previous != .dot and !(index + 1 < state.tokens.len and state.tokens[index + 1].kind == .equal)) {
|
||||
match kind {
|
||||
@@ -1777,6 +1803,9 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
state.next_token += 1
|
||||
_ = try consume(state, .open_paren)
|
||||
previous :: state.allow_record_literals
|
||||
previous_bit_or :: state.allow_bit_or
|
||||
state.allow_bit_or = true
|
||||
defer state.allow_bit_or = previous_bit_or
|
||||
state.allow_record_literals = true
|
||||
defer state.allow_record_literals = previous
|
||||
state.paren_depth += 1
|
||||
@@ -1812,6 +1841,9 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
.open_paren: {
|
||||
state.next_token += 1
|
||||
previous :: state.allow_record_literals
|
||||
previous_bit_or :: state.allow_bit_or
|
||||
state.allow_bit_or = true
|
||||
defer state.allow_bit_or = previous_bit_or
|
||||
state.allow_record_literals = true
|
||||
defer state.allow_record_literals = previous
|
||||
state.paren_depth += 1
|
||||
@@ -1910,7 +1942,7 @@ skip_operand_newlines proc(state @mut State) void {
|
||||
# a missing operand must not consume the declaration or assignment on the next line.
|
||||
if (
|
||||
named_binding_starts(state)
|
||||
or (next_is(state, .ident) and next_n_is(state, 1, .equal))
|
||||
or (next_is(state, .ident) and is_assignment_operator(state.tokens[state.next_token + 1].kind))
|
||||
) state.next_token = start
|
||||
}
|
||||
|
||||
@@ -2083,7 +2115,7 @@ consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! Syn
|
||||
@hide:file
|
||||
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
||||
return match kind {
|
||||
.minus, .bang, .ampersand, .try: BindingPowerScalar(50)
|
||||
.minus, .bang, .tilde, .ampersand, .try: BindingPowerScalar(50)
|
||||
else: null
|
||||
}
|
||||
}
|
||||
@@ -2095,6 +2127,8 @@ infix_binding_power proc(kind lexer.TokenKind) ?BindingPower {
|
||||
.and: BindingPower{ left = BindingPowerScalar(4), right = BindingPowerScalar(5) }
|
||||
.equal_equal, .not_equal: BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) }
|
||||
.less, .less_equal, .greater, .greater_equal: BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) }
|
||||
.ampersand, .xor, .pipe: BindingPower{ left = BindingPowerScalar(22), right = BindingPowerScalar(23) }
|
||||
.shift_left, .shift_right, .shift_left_saturating: BindingPower{ left = BindingPowerScalar(25), right = BindingPowerScalar(26) }
|
||||
.plus, .minus: BindingPower{ left = BindingPowerScalar(30), right = BindingPowerScalar(31) }
|
||||
.star, .slash: BindingPower{ left = BindingPowerScalar(40), right = BindingPowerScalar(41) }
|
||||
else: null
|
||||
@@ -2110,6 +2144,12 @@ binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator {
|
||||
.minus: .subtract
|
||||
.star: .multiply
|
||||
.slash: .divide
|
||||
.ampersand: .bit_and
|
||||
.pipe: .bit_or
|
||||
.xor: .bit_xor
|
||||
.shift_left: .shift_left
|
||||
.shift_right: .shift_right
|
||||
.shift_left_saturating: .shift_left_saturating
|
||||
.equal_equal: .equal
|
||||
.not_equal: .not_equal
|
||||
.less: .less
|
||||
@@ -2119,3 +2159,25 @@ binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator {
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
is_assignment_operator proc(kind lexer.TokenKind) bool {
|
||||
return kind == .equal or assignment_operator(kind) != null
|
||||
}
|
||||
|
||||
@hide:file
|
||||
assignment_operator proc(kind lexer.TokenKind) ?ast.BinaryOperator {
|
||||
return match kind {
|
||||
.plus_equal: .add
|
||||
.minus_equal: .subtract
|
||||
.star_equal: .multiply
|
||||
.slash_equal: .divide
|
||||
.ampersand_equal: .bit_and
|
||||
.pipe_equal: .bit_or
|
||||
.xor_equal: .bit_xor
|
||||
.shift_left_equal: .shift_left
|
||||
.shift_right_equal: .shift_right
|
||||
.shift_left_saturating_equal: .shift_left_saturating
|
||||
else: null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1544,7 +1544,7 @@ preserves_array_error_ids_control_bodies_and_following_siblings test {
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`main proc() void {
|
||||
` bad [~]i64 :: [~, 2]
|
||||
` bad [%]i64 :: [%, 2]
|
||||
` while (items[0) { break }
|
||||
` for items[0 |i| { continue }
|
||||
` unfinished :: [1,
|
||||
@@ -2242,3 +2242,136 @@ keeps_yielding_match_and_catch_bodies_as_raw_blocks test {
|
||||
try testing.expect_equal(ast.ExprKind.comptime_expr, ast.expr_kind(&tree, constant))
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, ast.comptime_operand(&tree, constant)))
|
||||
}
|
||||
|
||||
parses_bitwise_precedence_and_left_associativity test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source :: "bits :: ~a + b * c << d >> e <<| f & g xor h | i < j == k and l or m\n"
|
||||
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))
|
||||
expression := ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
|
||||
operators [11]ast.BinaryOperator :: [
|
||||
.logical_or, .logical_and, .equal, .less,
|
||||
.bit_or, .bit_xor, .bit_and,
|
||||
.shift_left_saturating, .shift_right, .shift_left, .add,
|
||||
]
|
||||
for (operators) |operator| {
|
||||
binary :: ast.binary_expr(&tree, expression)
|
||||
try testing.expect_equal(operator, binary.operator)
|
||||
if (operator == .add) {
|
||||
product :: ast.binary_expr(&tree, binary.right)
|
||||
try testing.expect_equal(ast.BinaryOperator.multiply, product.operator)
|
||||
}
|
||||
expression = binary.left
|
||||
}
|
||||
complement :: ast.unary_expr(&tree, expression)
|
||||
try testing.expect_equal(ast.UnaryOperator.bit_not, complement.operator)
|
||||
try testing.expect_equal("a", ast.source_text(&tree, ast.node_span(&tree, complement.operand)))
|
||||
}
|
||||
|
||||
parses_compound_operators_in_statement_contexts test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`run proc() void {
|
||||
` items[next()].field += rhs()
|
||||
` value -= 1
|
||||
` value *= 2
|
||||
` value /= 3
|
||||
` value &= mask
|
||||
` value |= mask
|
||||
` value xor= mask
|
||||
` value <<= count
|
||||
` value >>= count
|
||||
` value <<|= count
|
||||
` value = 4
|
||||
` match value { 0: value += 1, else: value -= 1 }
|
||||
` result :: { value |= mask }
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
builder := ast.init_builder(mem.c_allocator, source)
|
||||
defer ast.deinit_builder(&builder)
|
||||
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
|
||||
tree := ast.view(&builder, root)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
||||
operators [10]ast.BinaryOperator :: [
|
||||
.add, .subtract, .multiply, .divide, .bit_and, .bit_or, .bit_xor,
|
||||
.shift_left, .shift_right, .shift_left_saturating,
|
||||
]
|
||||
for (operators) |operator, index| {
|
||||
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, index))
|
||||
try testing.expect(assignment.compound)
|
||||
binary :: ast.binary_expr(&tree, assignment.value)
|
||||
try testing.expect_equal(operator, binary.operator)
|
||||
}
|
||||
try testing.expect(!ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 10)).compound)
|
||||
match_value :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 11))
|
||||
for (usize(0)..usize(2)) |index| {
|
||||
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, match_value, index))
|
||||
try testing.expect(!arm.expression)
|
||||
try testing.expect(ast.assignment_stmt(&tree, ast.block_statement(&tree, arm.body, 0)).compound)
|
||||
}
|
||||
block_value :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 12)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, block_value))
|
||||
}
|
||||
|
||||
keeps_type_composition_and_capture_pipes_with_bitwise_or test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`Alias :: A | B
|
||||
`Error :: A | B
|
||||
`Fallible :: T ! (A | B)
|
||||
`run proc(value A | B) T ! (A | B) {
|
||||
` if (flags | mask) |item| { item }
|
||||
` while (flags | mask) |item| { break }
|
||||
` for (flags | mask) |item, index| { continue }
|
||||
` if value |item| { item }
|
||||
` while value |item| { break }
|
||||
` for values |item| { continue }
|
||||
` if check_flags(flags | mask) { return }
|
||||
` while values[flags | mask] { break }
|
||||
`}
|
||||
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))
|
||||
for (usize(0)..usize(2)) |index| {
|
||||
expression :: ast.statement_decl(&tree, ast.package_decl(&tree, index)).initializer
|
||||
try testing.expect_equal(ast.BinaryOperator.bit_or, ast.binary_expr(&tree, expression).operator)
|
||||
}
|
||||
fallible :: ast.statement_decl(&tree, ast.package_decl(&tree, 2)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.fallible_type, ast.expr_kind(&tree, fallible))
|
||||
try testing.expect_equal(ast.ExprKind.sum_type, ast.expr_kind(&tree, ast.binary_expr(&tree, fallible).right))
|
||||
function_id :: ast.package_decl(&tree, 3)
|
||||
annotation :: ast.param(&tree, ast.function_param(&tree, function_id, 0)).type
|
||||
try testing.expect_equal(ast.ExprKind.sum_type, ast.expr_kind(&tree, annotation))
|
||||
body :: ast.function_decl(&tree, function_id).body
|
||||
condition :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 0))
|
||||
try testing.expect(condition.capture != null)
|
||||
try testing.expect_equal(ast.BinaryOperator.bit_or, ast.binary_expr(&tree, condition.condition).operator)
|
||||
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 1))
|
||||
try testing.expect(loop.capture != null)
|
||||
try testing.expect_equal(ast.BinaryOperator.bit_or, ast.binary_expr(&tree, loop.condition).operator)
|
||||
iteration :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 2))
|
||||
try testing.expect(iteration.capture != null and iteration.index != null)
|
||||
try testing.expect_equal(ast.BinaryOperator.bit_or, ast.binary_expr(&tree, iteration.iterable).operator)
|
||||
try testing.expect(ast.if_stmt(&tree, ast.block_statement(&tree, body, 3)).capture != null)
|
||||
try testing.expect(ast.while_stmt(&tree, ast.block_statement(&tree, body, 4)).capture != null)
|
||||
try testing.expect(ast.for_stmt(&tree, ast.block_statement(&tree, body, 5)).capture != null)
|
||||
}
|
||||
|
||||
+105
-7
@@ -438,7 +438,7 @@ check_elaborated proc(
|
||||
match ast.expr_kind(tree, id) {
|
||||
.unary: {
|
||||
expression :: ast.unary_expr(tree, id)
|
||||
if (expression.operator == .negate) try require_numeric(&state, id, expression.operand)
|
||||
if (expression.operator == .negate or expression.operator == .bit_not) try require_numeric(&state, id, expression.operand)
|
||||
}
|
||||
.binary: {
|
||||
expression :: ast.binary_expr(tree, id)
|
||||
@@ -464,7 +464,7 @@ check_elaborated proc(
|
||||
match ast.expr_kind(tree, id) {
|
||||
.unary: {
|
||||
expression :: ast.unary_expr(tree, id)
|
||||
if (expression.operator == .negate) try numeric_operand(&state, id, expression.operand)
|
||||
if (expression.operator == .negate or expression.operator == .bit_not) try numeric_operand(&state, id, expression.operand)
|
||||
}
|
||||
.binary: {
|
||||
expression :: ast.binary_expr(tree, id)
|
||||
@@ -473,7 +473,11 @@ check_elaborated proc(
|
||||
if (!bool_equality(&state, id)) try comparison_operands(&state, id)
|
||||
} else {
|
||||
try numeric_operand(&state, id, expression.left)
|
||||
try numeric_operand(&state, id, expression.right)
|
||||
if (shift(expression.operator)) {
|
||||
count_group :: root(&state, usize(expression.right))
|
||||
if (state.cells[count_group].type.kind == .none and state.cells[count_group].demand == .none)
|
||||
try impose(&state, expression.right, .usize, .invalid_operand, expression.span)
|
||||
} else try numeric_operand(&state, id, expression.right)
|
||||
if (expression.operator == .divide) {
|
||||
try impose(&state, id, .float, .integer_division, expression.span)
|
||||
}
|
||||
@@ -1370,10 +1374,10 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
}
|
||||
.unary: {
|
||||
expression :: ast.unary_expr(tree, id)
|
||||
if (expression.operator == .negate) state.cells[usize(id)].producer = id
|
||||
if (expression.operator == .negate or expression.operator == .bit_not) state.cells[usize(id)].producer = id
|
||||
if (expression.operator == .logical_not) {
|
||||
state.cells[usize(id)].type = Type{ kind = .bool }
|
||||
} else if (expression.operator == .negate) {
|
||||
} else if (expression.operator == .negate or expression.operator == .bit_not) {
|
||||
state.cells[usize(id)].numeric = true
|
||||
}
|
||||
operand :: expression.operand
|
||||
@@ -1570,6 +1574,27 @@ logical proc(operator ast.BinaryOperator) bool {
|
||||
return operator == .logical_and or operator == .logical_or
|
||||
}
|
||||
|
||||
@hide:file
|
||||
shift proc(operator ast.BinaryOperator) bool {
|
||||
return operator == .shift_left or operator == .shift_right or operator == .shift_left_saturating
|
||||
}
|
||||
|
||||
@hide:file
|
||||
bitwise proc(operator ast.BinaryOperator) bool {
|
||||
return operator == .bit_and or operator == .bit_or or operator == .bit_xor or shift(operator)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
integer_width proc(kind TypeKind) usize {
|
||||
return match kind {
|
||||
.i8, .u8: 8
|
||||
.i16, .u16: 16
|
||||
.i32, .u32: 32
|
||||
.int, .isize, .u64, .usize: 64
|
||||
else: 0
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
pointer_addition proc(state @mut State, id ast.NodeId) bool {
|
||||
expression :: ast.binary_expr(state.tree, id)
|
||||
@@ -2153,7 +2178,9 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
return
|
||||
}
|
||||
operand :: state.types[usize(expression.operand)]
|
||||
if (operand.kind != .invalid and (!numeric_kind(operand.kind) or unsigned_kind(operand.kind))) try fail(state, id, .invalid_operand, ast.node_span(tree, id))
|
||||
if (expression.operator == .bit_not) {
|
||||
if (operand.kind != .invalid and !integer_kind(operand.kind)) try fail(state, id, .invalid_operand, expression.span)
|
||||
} else if (operand.kind != .invalid and (!numeric_kind(operand.kind) or unsigned_kind(operand.kind))) try fail(state, id, .invalid_operand, ast.node_span(tree, id))
|
||||
}
|
||||
.binary: {
|
||||
expression :: ast.binary_expr(tree, id)
|
||||
@@ -2165,6 +2192,21 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
left :: state.types[usize(expression.left)]
|
||||
right :: state.types[usize(expression.right)]
|
||||
if (left.kind == .invalid or right.kind == .invalid) return
|
||||
if (shift(expression.operator)) {
|
||||
if (!integer_kind(left.kind) or !unsigned_kind(right.kind)) {
|
||||
try fail(state, id, .invalid_operand, expression.span)
|
||||
} else if (expression.operator != .shift_left_saturating) {
|
||||
if (evaluate_integer(state, expression.right, true)) |count| {
|
||||
if (count.negative or count.magnitude >= u64(integer_width(left.kind)))
|
||||
try fail(state, id, .invalid_shift_count, expression.span)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if (bitwise(expression.operator) and (!integer_kind(left.kind) or !integer_kind(right.kind))) {
|
||||
try fail(state, id, .invalid_operand, expression.span)
|
||||
return
|
||||
}
|
||||
if (pointer_addition(state, id)) {
|
||||
if (right.kind != .usize or right.nominal != ast.NodeId(maxval!(u32))) try fail(state, id, .invalid_operand, expression.span)
|
||||
return
|
||||
@@ -2378,6 +2420,53 @@ constant_integer proc(state @mut State, id ast.NodeId) ?ConstantInteger {
|
||||
return evaluate_integer(state, id, false)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
constant_bits proc(value ConstantInteger, mask u64) u64 {
|
||||
if (value.negative) return (value.magnitude - 1) xor mask
|
||||
return value.magnitude & mask
|
||||
}
|
||||
|
||||
@hide:file
|
||||
constant_from_bits proc(bits u64, kind TypeKind) ConstantInteger {
|
||||
mask :: maxval!(u64) >> u64(64 - integer_width(kind))
|
||||
negative :: !unsigned_kind(kind) and bits > (mask >> 1)
|
||||
magnitude :: if (negative) 1 + (bits xor mask) else bits
|
||||
return ConstantInteger{
|
||||
magnitude = magnitude,
|
||||
negative = negative,
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
constant_bitwise proc(state @mut State, id ast.NodeId, left, right ConstantInteger) ?ConstantInteger {
|
||||
view :: ast.binary_expr(state.tree, id)
|
||||
family :: bound_family(state, id)
|
||||
kind :: if (family == .none) TypeKind.int else family
|
||||
width :: integer_width(kind)
|
||||
if (width == 0 or !integer_fits(left, kind)) return null
|
||||
mask :: maxval!(u64) >> u64(64 - width)
|
||||
a :: constant_bits(left, mask)
|
||||
b :: constant_bits(right, mask)
|
||||
if (view.operator == .bit_and) return constant_from_bits(a & b, kind)
|
||||
if (view.operator == .bit_or) return constant_from_bits(a | b, kind)
|
||||
if (view.operator == .bit_xor) return constant_from_bits(a xor b, kind)
|
||||
if (right.negative) return null
|
||||
count :: right.magnitude
|
||||
if (view.operator == .shift_left_saturating) {
|
||||
signed_limit :: if (left.negative) 1 + (mask >> 1) else mask >> 1
|
||||
limit :: if (unsigned_kind(kind)) mask else signed_limit
|
||||
if (left.magnitude == 0) return left
|
||||
if (count >= u64(width) or left.magnitude > (limit >> count))
|
||||
return ConstantInteger{ magnitude = limit, negative = left.negative }
|
||||
} else if (count >= u64(width)) return null
|
||||
if (view.operator == .shift_right) {
|
||||
shifted :: a >> count
|
||||
bits :: if (left.negative) shifted | (mask xor (mask >> count)) else shifted
|
||||
return constant_from_bits(bits, kind)
|
||||
}
|
||||
return constant_from_bits((a << count) & mask, kind)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?ConstantInteger {
|
||||
if (state.cells[usize(id)].constant_visiting) return null
|
||||
@@ -2431,8 +2520,15 @@ evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?Consta
|
||||
}
|
||||
.unary: {
|
||||
view :: ast.unary_expr(state.tree, id)
|
||||
if (view.operator != .negate) return null
|
||||
if (view.operator != .negate and view.operator != .bit_not) return null
|
||||
if (evaluate_integer(state, view.operand, allow_local)) |value| {
|
||||
if (view.operator == .bit_not) {
|
||||
family :: bound_family(state, id)
|
||||
kind :: if (family == .none) TypeKind.int else family
|
||||
if (!integer_kind(kind) or !integer_fits(value, kind)) return null
|
||||
mask :: maxval!(u64) >> u64(64 - integer_width(kind))
|
||||
return constant_from_bits(constant_bits(value, mask) xor mask, kind)
|
||||
}
|
||||
return ConstantInteger{ magnitude = value.magnitude, negative = value.magnitude != 0 and !value.negative }
|
||||
}
|
||||
return null
|
||||
@@ -2443,6 +2539,7 @@ evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?Consta
|
||||
right := ConstantInteger{ magnitude = 0 }
|
||||
if (evaluate_integer(state, view.left, allow_local)) |value| left = value else return null
|
||||
if (evaluate_integer(state, view.right, allow_local)) |value| right = value else return null
|
||||
if (bitwise(view.operator)) return constant_bitwise(state, id, left, right)
|
||||
if (view.operator == .multiply) {
|
||||
if (right.magnitude != 0 and left.magnitude > divtrunc!(maxval!(u64), right.magnitude)) return null
|
||||
magnitude :: left.magnitude * right.magnitude
|
||||
@@ -3422,6 +3519,7 @@ bound_family proc(state @mut State, id ast.NodeId) TypeKind {
|
||||
if (ast.expr_kind(state.tree, producer) == .unary) return bound_family(state, ast.unary_expr(state.tree, producer).operand)
|
||||
view :: ast.binary_expr(state.tree, producer)
|
||||
left :: bound_family(state, view.left)
|
||||
if (shift(view.operator)) return left
|
||||
right :: bound_family(state, view.right)
|
||||
if (left == .float or right == .float) return .float
|
||||
if (integer_kind(left)) return left
|
||||
|
||||
@@ -718,7 +718,7 @@ resolves_every_array_child_error_and_preserves_lexer_diagnostics test {
|
||||
` return
|
||||
` data [missing_count]MissingElement :: [absent, missing_element]
|
||||
` (missing_base[missing_index])[missing_outer] = missing_rhs
|
||||
` malformed [~]i64 :: [~]
|
||||
` malformed [%]i64 :: [%]
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
|
||||
@@ -125,6 +125,8 @@ elaborate proc(
|
||||
if (class == .expression
|
||||
and (ast.expr_kind(&original, id) == .comptime_expr or ast.expr_kind(&original, id) == .block_expr))
|
||||
needed = true
|
||||
if (class == .expression and ast.expr_kind(&original, id) == .binary
|
||||
and ast.binary_expr(&original, id).operator == .bit_or) needed = true
|
||||
if (class == .statement and ast.stmt_kind(&original, id) == .for_stmt
|
||||
and ast.for_stmt(&original, id).is_inline) needed = true
|
||||
if (class == .match_arm and ast.match_arm_view(&original, id).is_inline) needed = true
|
||||
@@ -451,6 +453,14 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
}
|
||||
if (class == .expression and ast.expr_kind(&state.tree, id) == .binary) {
|
||||
view :: ast.binary_expr(&state.tree, id)
|
||||
if (view.operator == .bit_or
|
||||
and (is_type(state, try expression_type(state, view.left, 0))
|
||||
or is_type(state, try expression_type(state, view.right, 0)))) {
|
||||
composed :: try ast.add_binary_type(state.builder, view.span, view.left, view.right, false)
|
||||
try replace(state, id, composed)
|
||||
try refresh(state)
|
||||
return
|
||||
}
|
||||
if (
|
||||
(view.operator == .equal or view.operator == .not_equal)
|
||||
and branch_constant(state, view.left, 0) and branch_constant(state, view.right, 0)
|
||||
@@ -1502,7 +1512,7 @@ branch_constant proc(state @State, id ast.NodeId, depth usize) bool {
|
||||
.unary: {
|
||||
view :: ast.unary_expr(&state.tree, id)
|
||||
return (
|
||||
(view.operator == .negate or view.operator == .logical_not)
|
||||
(view.operator == .negate or view.operator == .logical_not or view.operator == .bit_not)
|
||||
and branch_constant(state, view.operand, depth + 1)
|
||||
)
|
||||
}
|
||||
@@ -1955,6 +1965,15 @@ contextual_literal proc(state @State, id ast.NodeId) bool {
|
||||
kind :: ast.expr_kind(&state.tree, id)
|
||||
if (kind == .builtin_call)
|
||||
return mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name), "some")
|
||||
if (kind == .unary) return ast.unary_expr(&state.tree, id).operator == .bit_not and flexible(state, id)
|
||||
if (kind == .binary) {
|
||||
operator :: ast.binary_expr(&state.tree, id).operator
|
||||
return (
|
||||
(operator == .bit_and or operator == .bit_or or operator == .bit_xor
|
||||
or operator == .shift_left or operator == .shift_right or operator == .shift_left_saturating)
|
||||
and flexible(state, id)
|
||||
)
|
||||
}
|
||||
return kind == .record_literal or kind == .array_literal or kind == .enum_literal or kind == .block_expr
|
||||
}
|
||||
|
||||
@@ -3963,6 +3982,7 @@ execute_type_statement_body proc(state @mut State, execution @mut TypeExecution,
|
||||
if (target != assignment.target) {
|
||||
normalized :: try ast.add_assignment_stmt(state.builder,
|
||||
ast.node_span(&state.tree, statement), target, assignment.value)
|
||||
if (assignment.compound) ast.set_assignment_compound(state.builder, normalized)
|
||||
try refresh(state)
|
||||
# synthesized places still need local substitution in snapshots.
|
||||
previous :: execution.mapping.len
|
||||
@@ -4442,6 +4462,8 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
||||
.alias_expr: return try evaluate(state, ast.type_operand(&state.tree, id))
|
||||
.unary: {
|
||||
view :: ast.unary_expr(&state.tree, id)
|
||||
if (view.operator == .bit_not)
|
||||
return try evaluate_ir(state, id, try expression_type(state, id, 0))
|
||||
value := try evaluate(state, view.operand)
|
||||
if (view.operator == .negate and value.kind == .integer) value.negative = value.magnitude != 0 and !value.negative
|
||||
else if (view.operator == .negate and value.kind == .real) value.magnitude = value.magnitude xor (u64(1) << 63)
|
||||
@@ -4484,6 +4506,9 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
||||
evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
|
||||
view :: ast.binary_expr(&state.tree, id)
|
||||
if (view.operator == .divide) return try evaluate_ir(state, id, try builtin(state, id, .float))
|
||||
if (view.operator == .bit_and or view.operator == .bit_or or view.operator == .bit_xor
|
||||
or view.operator == .shift_left or view.operator == .shift_right or view.operator == .shift_left_saturating)
|
||||
return try evaluate_ir(state, id, try expression_type(state, id, 0))
|
||||
left :: try evaluate(state, view.left)
|
||||
if (left.kind == .invalid) return Value{}
|
||||
if (left.kind == .boolean) {
|
||||
@@ -4978,6 +5003,8 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
||||
or view.operator == .less_equal or view.operator == .greater or view.operator == .greater_equal
|
||||
or view.operator == .logical_and or view.operator == .logical_or
|
||||
) return try builtin(state, id, .bool)
|
||||
if ((view.operator == .bit_and or view.operator == .bit_or or view.operator == .bit_xor)
|
||||
and flexible(state, view.left)) return try expression_type(state, view.right, depth + 1)
|
||||
return try expression_type(state, view.left, depth + 1)
|
||||
}
|
||||
.selection: {
|
||||
@@ -5326,12 +5353,14 @@ flexible proc(state @State, id ast.NodeId) bool {
|
||||
if (kind == .literal_int or kind == .literal_float) return true
|
||||
if (kind == .unary) {
|
||||
view :: ast.unary_expr(&state.tree, id)
|
||||
return view.operator == .negate and flexible(state, view.operand)
|
||||
return (view.operator == .negate or view.operator == .bit_not) and flexible(state, view.operand)
|
||||
}
|
||||
if (kind == .binary) {
|
||||
view :: ast.binary_expr(&state.tree, id)
|
||||
return (
|
||||
(view.operator == .add or view.operator == .subtract or view.operator == .multiply or view.operator == .divide)
|
||||
(view.operator == .add or view.operator == .subtract or view.operator == .multiply or view.operator == .divide
|
||||
or view.operator == .bit_and or view.operator == .bit_or or view.operator == .bit_xor
|
||||
or view.operator == .shift_left or view.operator == .shift_right or view.operator == .shift_left_saturating)
|
||||
and flexible(state, view.left) and flexible(state, view.right)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user