feat: support compound updates and bitwise operators
This commit is contained in:
@@ -160,9 +160,13 @@ callback signatures use `@proc(value i16) i16`, including grouped parameter name
|
|||||||
|
|
||||||
`name = expression` updates a local `:=` binding. `::` bindings, parameters, named functions, builtin types and globals are not assignment targets. the established binding type is preserved: assignment inputs do not widen or infer that type, though numeric literals can adopt its float context. function-valued locals accept replacement functions with the same parameter and result types.
|
`name = expression` updates a local `:=` binding. `::` bindings, parameters, named functions, builtin types and globals are not assignment targets. the established binding type is preserved: assignment inputs do not widen or infer that type, though numeric literals can adopt its float context. function-valued locals accept replacement functions with the same parameter and result types.
|
||||||
|
|
||||||
|
compound updates `+= -= *= /= &= |= xor= <<= >>= <<|=` preserve the target type and ordinary writable-place checks. the target address and old value are evaluated once before the right operand, then the result is stored through that same address; `/=` is float-only and `xor=` must be contiguous. field/index/pointer targets, cleanup bodies, ordinary compile-time calls and type-producing locals use the same checked operations.
|
||||||
|
|
||||||
|
integer `~`, `&`, `xor` and `|` preserve exact operand types, including distinct integer identities; incompatible fixed integer types remain invalid. shifts `<<`, `>>` and saturating `<<|` preserve the left type and accept unsigned counts independently of its width. ordinary shifts reject known out-of-range counts and trap for runtime counts at least the left bit width. left shifts discard high bits; signed right shifts extend the sign. saturating shifts accept any count, preserve zero and clamp nonzero values to the signed minimum/maximum or unsigned maximum. shifts bind below arithmetic; `&`, `xor` and `|` share one left-associative level below shifts and above comparisons. parenthesize bitwise-OR header expressions before capture lists. type-valued `A | B` remains error-set composition.
|
||||||
|
|
||||||
fixed arrays use `[N]T` for readonly elements and `[N]mut T` for mutable elements; literals use `[a, b]`, including empty, nested and multiline forms with trailing commas. `values [4]mut i64 := [1, 2, 3, 4]` permits `values[2] = 10`. writable elements require a mutable local binding and `mut` at every traversed array level; parameters and globals remain readonly. whole-array assignment only requires a mutable binding, but source and destination types must match recursively, including counts and element mutability. initializations, assignments, arguments and returns copy arrays by value, including nested rows.
|
fixed arrays use `[N]T` for readonly elements and `[N]mut T` for mutable elements; literals use `[a, b]`, including empty, nested and multiline forms with trailing commas. `values [4]mut i64 := [1, 2, 3, 4]` permits `values[2] = 10`. writable elements require a mutable local binding and `mut` at every traversed array level; parameters and globals remain readonly. whole-array assignment only requires a mutable binding, but source and destination types must match recursively, including counts and element mutability. initializations, assignments, arguments and returns copy arrays by value, including nested rows.
|
||||||
|
|
||||||
array lengths currently support nonnegative integer literals, unary signs, `+`, `-`, `*` and package-level immutable integer constants; local constants and other compile-time expressions are not implemented. `[_]` infers a binding's array length from its initializer. element annotations require concrete supported value types: use `i64` or `f64` rather than the `int` or `float` constraints. an unannotated array literal has readonly elements.
|
array lengths support nonnegative integer literals, unary signs/complement, `+`, `-`, `*`, bitwise operators/shifts and package-level immutable integer constants; use `$()` for broader compile-time execution. `[_]` infers a binding's array length from its initializer. element annotations require concrete supported value types: use `i64` or `f64` rather than the `int` or `float` constraints. an unannotated array literal has readonly elements.
|
||||||
|
|
||||||
indices require `usize`; integer literals and flexible inferred values receive this context, while fixed signed integers do not convert implicitly. out-of-bounds access traps before loading or storing, including for empty arrays. reads evaluate the base before the index; indexed assignment evaluates its right-hand side before the destination, each once. leading nested assignment targets follow Bro's declaration ambiguity: write `(matrix[1])[0] = 9`, not `matrix[1][0] = 9`. [programs/array_sum.hon](programs/array_sum.hon) uses a `usize` range bound to index, mutate and sum an array; `./build/honey run programs/array_sum.hon` prints `17`.
|
indices require `usize`; integer literals and flexible inferred values receive this context, while fixed signed integers do not convert implicitly. out-of-bounds access traps before loading or storing, including for empty arrays. reads evaluate the base before the index; indexed assignment evaluates its right-hand side before the destination, each once. leading nested assignment targets follow Bro's declaration ambiguity: write `(matrix[1])[0] = 9`, not `matrix[1][0] = 9`. [programs/array_sum.hon](programs/array_sum.hon) uses a `usize` range bound to index, mutate and sum an array; `./build/honey run programs/array_sum.hon` prints `17`.
|
||||||
|
|
||||||
@@ -269,4 +273,4 @@ IR construction owns derived layout metadata. `ir.add_array`, `ir.add_record` an
|
|||||||
|
|
||||||
`interpreter.run` consumes only IR and returns an `Outcome` containing a value or diagnostic trap. returned aggregates own their escaped cell storage; release every outcome with `interpreter.deinit` before releasing its module. string values carry a module-local `ir.StringId`, not a slice; resolve bytes with `ir.string_bytes(&module, id)` before releasing the module. decoded bytes and their full backing allocations remain module-owned, without copying strings during calls or returns. aggregate descriptors also borrow the module; `ir.aggregate_item` projects a borrowed field/element while preserving zero-cell child shapes. heap-backed invocation frames track their current block and resume it after calls or global initialization; loop iterations reuse the invocation's storage without allocating new frames. parameters are initialized on entry and local declarations execute in statement order. premature local reads and assignments trap; assignment evaluates its right-hand side before checking the destination's initialization. globals initialize lazily on their first read, cache their result for that run, and trap on initialization cycles. `int`/`i64` use signed 64-bit storage, `usize` uses unsigned 64-bit storage, both with checked arithmetic; `float`/`f64` use 64-bit IEEE arithmetic and comparisons, including NaN behavior. `usize` supports `+`, `-`, `*` and comparisons, but not unary negation or division. numeric literal overflow and float underflow to zero are diagnosed; representable subnormals are accepted. strings support `\\`, `\"`, `\n`, `\r`, `\t`, `\0` and `\xNN` escapes.
|
`interpreter.run` consumes only IR and returns an `Outcome` containing a value or diagnostic trap. returned aggregates own their escaped cell storage; release every outcome with `interpreter.deinit` before releasing its module. string values carry a module-local `ir.StringId`, not a slice; resolve bytes with `ir.string_bytes(&module, id)` before releasing the module. decoded bytes and their full backing allocations remain module-owned, without copying strings during calls or returns. aggregate descriptors also borrow the module; `ir.aggregate_item` projects a borrowed field/element while preserving zero-cell child shapes. heap-backed invocation frames track their current block and resume it after calls or global initialization; loop iterations reuse the invocation's storage without allocating new frames. parameters are initialized on entry and local declarations execute in statement order. premature local reads and assignments trap; assignment evaluates its right-hand side before checking the destination's initialization. globals initialize lazily on their first read, cache their result for that run, and trap on initialization cycles. `int`/`i64` use signed 64-bit storage, `usize` uses unsigned 64-bit storage, both with checked arithmetic; `float`/`f64` use 64-bit IEEE arithmetic and comparisons, including NaN behavior. `usize` supports `+`, `-`, `*` and comparisons, but not unary negation or division. numeric literal overflow and float underflow to zero are diagnosed; representable subnormals are accepted. strings support `\\`, `\"`, `\n`, `\r`, `\t`, `\0` and `\xNN` escapes.
|
||||||
|
|
||||||
recoverable source errors do not stop execution of unaffected code. unused invalid functions, globals and nested bodies retain diagnostic traps; reaching invalid code reports its original diagnostic identity. missing-return traps affect only paths that fall through, not paths that already return or trap. diagnostics include the filename, line and byte column. exit status is `0` for clean execution, `1` for source diagnostics or a runtime trap, and `2` for usage or infrastructure failure. compound assignment remains unimplemented.
|
recoverable source errors do not stop execution of unaffected code. unused invalid functions, globals and nested bodies retain diagnostic traps; reaching invalid code reports its original diagnostic identity. missing-return traps affect only paths that fall through, not paths that already return or trap. diagnostics include the filename, line and byte column. exit status is `0` for clean execution, `1` for source diagnostics or a runtime trap, and `2` for usage or infrastructure failure.
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ runtime/constant values, contextual optional/fallible results and generated type
|
|||||||
yielding value blocks, match arms and catch handlers copy results before scoped
|
yielding value blocks, match arms and catch handlers copy results before scoped
|
||||||
cleanup and preserve enclosing function/loop transfers through both execution paths;
|
cleanup and preserve enclosing function/loop transfers through both execution paths;
|
||||||
compile-time blocks also generate types, storage and defaults.
|
compile-time blocks also generate types, storage and defaults.
|
||||||
|
compound updates evaluate places once, and integer bitwise/truncating/saturating
|
||||||
|
shifts preserve widths and nominal types through runtime and compile-time execution.
|
||||||
the driver retains frontend storage and source provenance through
|
the driver retains frontend storage and source provenance through
|
||||||
`@source/compilation`, with compilation-wide AST node identities.
|
`@source/compilation`, with compilation-wide AST node identities.
|
||||||
file commands retain single-file behavior; directory commands compile the
|
file commands retain single-file behavior; directory commands compile the
|
||||||
@@ -127,11 +129,12 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
- [x] type-metadata reflection checkpoint: `typeinfo!(T)` uses the actual `@std/meta` ABI to classify supported types and expose array child/length, immediate distinct backing, record names/tuple/layout and enum/record fields through interpreter/native execution. readonly owned metadata collections retain exact nominal/type leaves, ordinal indices, composed enum identity, checked projections, by-value iteration/copies and compile-time procedure boundaries. ordinary/inline matches select runtime bodies; reflected fields drive `field!`, generics and generated storage/defaults. arity/type/import/ABI failures, match/capture validation, bounds, readonly updates, runtime escape rejection, imported provenance, shared construction bounds and repeatable compilation retain diagnostics and ownership. existing `meta.EnumFieldStruct` and `enummap.EnumMap` generated storage are verified without replacement library code; full `enummap.init/get` is covered below, while general type-valued execution and broader constant materialization remain open.
|
- [x] type-metadata reflection checkpoint: `typeinfo!(T)` uses the actual `@std/meta` ABI to classify supported types and expose array child/length, immediate distinct backing, record names/tuple/layout and enum/record fields through interpreter/native execution. readonly owned metadata collections retain exact nominal/type leaves, ordinal indices, composed enum identity, checked projections, by-value iteration/copies and compile-time procedure boundaries. ordinary/inline matches select runtime bodies; reflected fields drive `field!`, generics and generated storage/defaults. arity/type/import/ABI failures, match/capture validation, bounds, readonly updates, runtime escape rejection, imported provenance, shared construction bounds and repeatable compilation retain diagnostics and ownership. existing `meta.EnumFieldStruct` and `enummap.EnumMap` generated storage are verified without replacement library code; full `enummap.init/get` is covered below, while general type-valued execution and broader constant materialization remain open.
|
||||||
- [x] enum-map acceptance checkpoint: existing `enummap.init/get` run through interpreter/native execution with sparse/aliased/composed enum keys, absent entries and owned aggregate values. contextual `.tag` and aggregate arguments reuse key/value types inferred from rigid generated-map arguments; unresolved generics and rigid conflicts still diagnose. `some!` constructs exact, single-evaluation optional payloads and preserves nested present-null values through compile-time results, cache keys and generated defaults. concrete mutable runtime locals initialize from `undefined`, including nested record/array updates, pointer access and by-value copies; ordinary compile-time procedures materialize fully initialized storage and reject incomplete snapshots. native undefined storage is poison, not zeroed or per-leaf checked. `get` now explicitly returns `null` on lookup fallthrough, preserving the ordinary nonvoid return contract.
|
- [x] enum-map acceptance checkpoint: existing `enummap.init/get` run through interpreter/native execution with sparse/aliased/composed enum keys, absent entries and owned aggregate values. contextual `.tag` and aggregate arguments reuse key/value types inferred from rigid generated-map arguments; unresolved generics and rigid conflicts still diagnose. `some!` constructs exact, single-evaluation optional payloads and preserves nested present-null values through compile-time results, cache keys and generated defaults. concrete mutable runtime locals initialize from `undefined`, including nested record/array updates, pointer access and by-value copies; ordinary compile-time procedures materialize fully initialized storage and reject incomplete snapshots. native undefined storage is poison, not zeroed or per-leaf checked. `get` now explicitly returns `null` on lookup fallthrough, preserving the ordinary nonvoid return contract.
|
||||||
- [x] array-list acceptance checkpoint: existing `std/arraylist` and `std/mem` initialize, reserve/grow, append/pop/clear and release ordinary/aggregate/zero-sized storage without replacement library code. nested generic inference canonicalizes generated types through pointers and derives exact slice `.ptr` types; rigid conflicts remain diagnostic. `minval!`/`maxval!` preserve integer types and checked `divtrunc!` traps on zero and signed overflow through interpreter/native execution. file-local helpers retain real `@hide:file` visibility, newline `else` preserves match default arms, and repeated `_` parameters remain sinks. generated C records, nominal opaque pointers and foreign callbacks load from existing `ffi/c`; unused unsupported signatures neither diagnose nor emit external symbols, while demanded unsupported ABI/types diagnose. interpreter fixed-capacity allocator checks cover failure without losing storage and deferred error cleanup; native `mem.c_allocator` checks cover aggregate growth and over-aligned allocation/reallocation at `-O0`/`-O2`. arbitrary interpreter FFI, variadic/aggregate C calls and broader library acceptance remain open.
|
- [x] array-list acceptance checkpoint: existing `std/arraylist` and `std/mem` initialize, reserve/grow, append/pop/clear and release ordinary/aggregate/zero-sized storage without replacement library code. nested generic inference canonicalizes generated types through pointers and derives exact slice `.ptr` types; rigid conflicts remain diagnostic. `minval!`/`maxval!` preserve integer types and checked `divtrunc!` traps on zero and signed overflow through interpreter/native execution. file-local helpers retain real `@hide:file` visibility, newline `else` preserves match default arms, and repeated `_` parameters remain sinks. generated C records, nominal opaque pointers and foreign callbacks load from existing `ffi/c`; unused unsupported signatures neither diagnose nor emit external symbols, while demanded unsupported ABI/types diagnose. interpreter fixed-capacity allocator checks cover failure without losing storage and deferred error cleanup; native `mem.c_allocator` checks cover aggregate growth and over-aligned allocation/reallocation at `-O0`/`-O2`. arbitrary interpreter FFI, variadic/aggregate C calls and broader library acceptance remain open.
|
||||||
- [x] boolean conditional-expression checkpoint: `if (guard) then_value else else_value` reuses canonical matches through interpreter/native execution. guards evaluate once and only the selected expression executes; nested conditionals bind `else` to the nearest conditional. exact result types, aggregate copy isolation, contextual optional/fallible channels, direct `$()` values, constant-input procedures, generic branch selection and nested generated type positions retain existing checked execution. missing `else`, nonboolean guards and incompatible runtime branch types diagnose. optional captures and yielding value blocks are covered below; compound updates and bitwise operators remain open before hash-map acceptance.
|
- [x] boolean conditional-expression checkpoint: `if (guard) then_value else else_value` reuses canonical matches through interpreter/native execution. guards evaluate once and only the selected expression executes; nested conditionals bind `else` to the nearest conditional. exact result types, aggregate copy isolation, contextual optional/fallible channels, direct `$()` values, constant-input procedures, generic branch selection and nested generated type positions retain existing checked execution. missing `else`, nonboolean guards and incompatible runtime branch types diagnose. optional captures, yielding value blocks, compound updates and bitwise operators are covered below.
|
||||||
- [x] optional conditional-expression checkpoint: `if value |payload| then_value else else_value` reuses canonical presence matches through interpreter/native execution. single-evaluation guards and lazy branches preserve exact immutable by-value captures, nested optional presence, aggregate isolation and runtime pointer/callback/slice/zero-sized payloads. contextual optional/fallible results, constant-input procedures, generated types/defaults and explicit compile-time subject selection retain checked execution and owned payload snapshots. nonoptional guards, capture scope/shadowing, malformed captures, missing `else`, incompatible branches and compile-time overflow retain diagnostics. yielding value blocks are covered below; broader constant materialization remains open.
|
- [x] optional conditional-expression checkpoint: `if value |payload| then_value else else_value` reuses canonical presence matches through interpreter/native execution. single-evaluation guards and lazy branches preserve exact immutable by-value captures, nested optional presence, aggregate isolation and runtime pointer/callback/slice/zero-sized payloads. contextual optional/fallible results, constant-input procedures, generated types/defaults and explicit compile-time subject selection retain checked execution and owned payload snapshots. nonoptional guards, capture scope/shadowing, malformed captures, missing `else`, incompatible branches and compile-time overflow retain diagnostics. yielding value blocks are covered below; broader constant materialization remains open.
|
||||||
- [x] yielding-block checkpoint: value blocks and `yield expression` produce exact/contextual results through interpreter/native execution, including conditional branches, match arms and catch handlers. nested value sources select the nearest target; ordinary blocks/loops preserve it. yielded aggregates snapshot before LIFO cleanup, successful yields skip `errdefer`, and return/error/break/continue retain enclosing targets. ordinary constants, generic branch specialization and type-producing blocks generate owned values, types, storage and defaults. missing operands/results, incompatible yields, capture mutation and outward cleanup transfers retain diagnostics and demand-driven traps; imported provenance and repeatable compilation are verified. labeled yields and value-producing loops remain outside this checkpoint.
|
- [x] yielding-block checkpoint: value blocks and `yield expression` produce exact/contextual results through interpreter/native execution, including conditional branches, match arms and catch handlers. nested value sources select the nearest target; ordinary blocks/loops preserve it. yielded aggregates snapshot before LIFO cleanup, successful yields skip `errdefer`, and return/error/break/continue retain enclosing targets. ordinary constants, generic branch specialization and type-producing blocks generate owned values, types, storage and defaults. missing operands/results, incompatible yields, capture mutation and outward cleanup transfers retain diagnostics and demand-driven traps; imported provenance and repeatable compilation are verified. labeled yields and value-producing loops remain outside this checkpoint.
|
||||||
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including untagged unions and storage-bearing values), plus full existing-library acceptance for hash maps, broader `meta` operations and `io.print`. required expression-valued control flow, compound updates, formatting parsing and broader imported C-binding support may interleave with later gates.
|
- [x] compound/bitwise checkpoint: arithmetic and bitwise compound updates evaluate mutable local/field/index/pointer places and their old values once before the RHS through interpreter/native execution. integer complement, and/or/xor, truncating left shifts, signed/unsigned right shifts and saturating left shifts preserve widths and distinct identity; unsigned counts retain independent types. known invalid counts diagnose even in untaken runtime branches; dynamic counts trap without native LLVM poison. checked arithmetic, float-only division, readonly diagnostics, scoped cleanup, ordinary constants, generated storage, generic branch selection and repeatable compilation retain existing semantics. type-valued `|` remains composition while runtime `|` and capture delimiters retain their grammar. full hash-map acceptance remains open.
|
||||||
- [ ] remaining source constructs: compound updates, bitwise operators and multiline strings. boolean and optional-capture conditional expressions, yielding value blocks and `yield` are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including untagged unions and storage-bearing values), plus full existing-library acceptance for hash maps, broader `meta` operations and `io.print`. formatting parsing and broader imported C-binding support may interleave with later gates.
|
||||||
|
- [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
||||||
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
||||||
- [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support.
|
- [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support.
|
||||||
- [ ] self-hosting proof: bootstrap-built Honey builds the production compiler (excluding test-only imports); that executable rebuilds the compiler; the rebuilt compiler passes representative interpreter/native examples and CLI/error scenarios without Bro at runtime.
|
- [ ] self-hosting proof: bootstrap-built Honey builds the production compiler (excluding test-only imports); that executable rebuilds the compiler; the rebuilt compiler passes representative interpreter/native examples and CLI/error scenarios without Bro at runtime.
|
||||||
|
|||||||
+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.
|
#! * 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 ... -> :: | :=
|
#! * binding ... -> :: | :=
|
||||||
#! * literal .. -> int | float | string | 'true' | 'false' | 'null'
|
#! * literal .. -> int | float | string | 'true' | 'false' | 'null'
|
||||||
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
#! * 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)
|
#! * 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
|
#! * 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.
|
#! * parameters -> [['$'] identifier {',' ['$'] identifier} type {',' ['$'] identifier {',' ['$'] identifier} type} [',']]; '$' marks each compile-time parameter independently.
|
||||||
#! '$(' expr ')' is an explicit compile-time expression with primary-expression precedence.
|
#! '$(' expr ')' is an explicit compile-time expression with primary-expression precedence.
|
||||||
@@ -14,9 +14,11 @@
|
|||||||
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
||||||
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
|
#! * 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.
|
#! 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
|
#! * 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
|
||||||
#! braceless guards/iterables require parentheses unless they are calls, explicit comptime expressions or optional captures; else binds to the nearest if.
|
#! assignment_op is '=' or a contiguous compound: '+=', '-=', '*=', '/=', '&=', '|=', 'xor=', '<<=', '>>=', '<<|='.
|
||||||
#! 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.
|
#! 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.
|
#! binary operators associate left; unary operators associate right.
|
||||||
|
|
||||||
import "@std/debug"
|
import "@std/debug"
|
||||||
@@ -118,6 +120,7 @@ Mutability :: enum {
|
|||||||
UnaryOperator :: enum {
|
UnaryOperator :: enum {
|
||||||
negate
|
negate
|
||||||
logical_not
|
logical_not
|
||||||
|
bit_not
|
||||||
address_of
|
address_of
|
||||||
dereference
|
dereference
|
||||||
unwrap
|
unwrap
|
||||||
@@ -126,6 +129,8 @@ UnaryOperator :: enum {
|
|||||||
BinaryOperator :: enum {
|
BinaryOperator :: enum {
|
||||||
add, subtract
|
add, subtract
|
||||||
multiply, divide
|
multiply, divide
|
||||||
|
bit_and, bit_or, bit_xor
|
||||||
|
shift_left, shift_right, shift_left_saturating
|
||||||
equal, not_equal
|
equal, not_equal
|
||||||
less, less_equal
|
less, less_equal
|
||||||
greater, greater_equal
|
greater, greater_equal
|
||||||
@@ -214,6 +219,7 @@ NodeData :: union {
|
|||||||
@hide:file variadic_flag u8 :: 0b0100
|
@hide:file variadic_flag u8 :: 0b0100
|
||||||
@hide:file opaque_flag u8 :: 0b1000
|
@hide:file opaque_flag u8 :: 0b1000
|
||||||
@hide:file optional_match_flag u8 :: 0b00010000
|
@hide:file optional_match_flag u8 :: 0b00010000
|
||||||
|
@hide:file compound_assignment_flag u8 :: 0b00100000
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
Node :: struct {
|
Node :: struct {
|
||||||
@@ -405,6 +411,7 @@ AssignmentView :: struct {
|
|||||||
span Span
|
span Span
|
||||||
target NodeId
|
target NodeId
|
||||||
value NodeId
|
value NodeId
|
||||||
|
compound bool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
WhileView :: struct {
|
WhileView :: struct {
|
||||||
@@ -1023,6 +1030,12 @@ add_assignment_stmt proc(
|
|||||||
}, span)
|
}, 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(
|
add_while_stmt proc(
|
||||||
builder @mut Builder,
|
builder @mut Builder,
|
||||||
span Span,
|
span Span,
|
||||||
@@ -1416,6 +1429,7 @@ assignment_stmt proc(tree @Tree, id NodeId) AssignmentView {
|
|||||||
span = get_span(&tree.storage, id),
|
span = get_span(&tree.storage, id),
|
||||||
target = node.main.node_id,
|
target = node.main.node_id,
|
||||||
value = node.data0.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)
|
assignment :: ast.assignment_stmt(tree, statement)
|
||||||
try append_text(renderer, "assignment ")
|
try append_text(renderer, "assignment ")
|
||||||
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, assignment.target)))
|
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, assignment.target)))
|
||||||
|
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, " = ")
|
||||||
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, assignment.value)))
|
}
|
||||||
|
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, value)))
|
||||||
try append_text(renderer, "\n")
|
try append_text(renderer, "\n")
|
||||||
}
|
}
|
||||||
.while_stmt: {
|
.while_stmt: {
|
||||||
@@ -151,3 +160,20 @@ render_statement proc(
|
|||||||
append_text proc(renderer @mut Renderer, text []u8) void ! mem.AllocError {
|
append_text proc(renderer @mut Renderer, text []u8) void ! mem.AllocError {
|
||||||
for (text) |byte| try arraylist.append(&renderer.bytes, byte)
|
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)
|
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
|
missing_metadata_import
|
||||||
invalid_metadata_abi
|
invalid_metadata_abi
|
||||||
invalid_undefined
|
invalid_undefined
|
||||||
|
invalid_shift_count
|
||||||
}
|
}
|
||||||
|
|
||||||
Diagnostic :: struct {
|
Diagnostic :: struct {
|
||||||
@@ -179,6 +180,7 @@ messages :: $enummap.init(Code, []u8, {
|
|||||||
type_mismatch = "incompatible value type",
|
type_mismatch = "incompatible value type",
|
||||||
invalid_operand = "operator requires compatible operand types",
|
invalid_operand = "operator requires compatible operand types",
|
||||||
integer_division = "division requires float operands",
|
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",
|
not_callable = "value is not callable",
|
||||||
argument_count_mismatch = "argument count does not match the function signature",
|
argument_count_mismatch = "argument count does not match the function signature",
|
||||||
cannot_infer_type = "cannot infer a type without a type anchor",
|
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)]
|
operand :: frames.items[frame_index].values[usize(instruction.left)]
|
||||||
value = ir.Value{ type = .bool, data = ir.ValueData{ boolean = !operand.data.boolean } }
|
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: {
|
.add, .subtract, .multiply, .divide: {
|
||||||
left :: frames.items[frame_index].values[usize(instruction.left)]
|
left :: frames.items[frame_index].values[usize(instruction.left)]
|
||||||
right :: frames.items[frame_index].values[usize(instruction.right)]
|
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) }
|
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
|
@hide:file
|
||||||
checked_integer proc(op ir.Op, left i64, right i64) ?i64 {
|
checked_integer proc(op ir.Op, left i64, right i64) ?i64 {
|
||||||
minimum :: minval!(i64)
|
minimum :: minval!(i64)
|
||||||
|
|||||||
+8
-12
@@ -155,22 +155,18 @@ Op :: enum {
|
|||||||
slice_check
|
slice_check
|
||||||
sentinel_check
|
sentinel_check
|
||||||
optional_check
|
optional_check
|
||||||
memory_copy
|
memory_copy, memory_set
|
||||||
memory_set
|
|
||||||
negate
|
negate
|
||||||
convert
|
convert
|
||||||
logical_not
|
logical_not
|
||||||
add
|
bit_not, bit_and, bit_or, bit_xor
|
||||||
subtract
|
shift_left, shift_right, shift_left_saturating
|
||||||
multiply
|
add, subtract
|
||||||
divide
|
multiply, divide
|
||||||
call
|
call
|
||||||
equal
|
equal, not_equal
|
||||||
not_equal
|
less, less_equal
|
||||||
less
|
greater, greater_equal
|
||||||
less_equal
|
|
||||||
greater
|
|
||||||
greater_equal
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Instruction :: struct {
|
Instruction :: struct {
|
||||||
|
|||||||
+34
-3
@@ -41,6 +41,7 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
|||||||
{ "false", .false },
|
{ "false", .false },
|
||||||
{ "and", .and },
|
{ "and", .and },
|
||||||
{ "or", .or },
|
{ "or", .or },
|
||||||
|
{ "xor", .xor },
|
||||||
{ "mut", .mut },
|
{ "mut", .mut },
|
||||||
{ "struct", .struct },
|
{ "struct", .struct },
|
||||||
{ "c_struct", .c_struct },
|
{ "c_struct", .c_struct },
|
||||||
@@ -107,12 +108,16 @@ scan proc(
|
|||||||
if is_alpha(char) or char == '_' {
|
if is_alpha(char) or char == '_' {
|
||||||
start :: cursor
|
start :: cursor
|
||||||
result :: try scan_ident(start, program, pool)
|
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{
|
try add_token(state, Token{
|
||||||
span = make_span(source, start, result.end),
|
span = make_span(source, start, end),
|
||||||
kind = result.kind,
|
kind = kind,
|
||||||
data = TokenData{ str_id = result.str_id },
|
data = TokenData{ str_id = result.str_id },
|
||||||
})
|
})
|
||||||
cursor = result.end
|
cursor = end
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,12 +286,37 @@ scan proc(
|
|||||||
continue
|
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] == '=') {
|
if (cursor + 1 < program.len and program[cursor + 1] == '=') {
|
||||||
comparison ?TokenKind :: match char {
|
comparison ?TokenKind :: match char {
|
||||||
'=': .equal_equal
|
'=': .equal_equal
|
||||||
'!': .not_equal
|
'!': .not_equal
|
||||||
'<': .less_equal
|
'<': .less_equal
|
||||||
'>': .greater_equal
|
'>': .greater_equal
|
||||||
|
'&': .ampersand_equal
|
||||||
|
'|': .pipe_equal
|
||||||
else: null
|
else: null
|
||||||
}
|
}
|
||||||
if (comparison) |value| {
|
if (comparison) |value| {
|
||||||
@@ -302,6 +332,7 @@ scan proc(
|
|||||||
kind ?TokenKind :: match char {
|
kind ?TokenKind :: match char {
|
||||||
'=': .equal
|
'=': .equal
|
||||||
'!': .bang
|
'!': .bang
|
||||||
|
'~': .tilde
|
||||||
'<': .less
|
'<': .less
|
||||||
'>': .greater
|
'>': .greater
|
||||||
',': .comma
|
',': .comma
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ reports_invalid_input_and_keeps_scanning test {
|
|||||||
state := init(mem.c_allocator)
|
state := init(mem.c_allocator)
|
||||||
defer deinit(&state)
|
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(9, state.tokens.items.len)
|
||||||
try testing.expect_equal(TokenKind.if, state.tokens.items[0].kind)
|
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)
|
state := init(mem.c_allocator)
|
||||||
defer deinit(&state)
|
defer deinit(&state)
|
||||||
state.source_id = diagnostics.SourceId(7)
|
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.import, state.tokens.items[0].kind)
|
||||||
try testing.expect_equal(TokenKind.test, state.tokens.items[1].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)
|
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("yield", strpool.get_str(&pool, state.tokens.items[0].data.str_id)?)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
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
|
defer, errdefer
|
||||||
true, false
|
true, false
|
||||||
and, or
|
and, or
|
||||||
|
xor
|
||||||
struct, c_struct
|
struct, c_struct
|
||||||
opaque
|
opaque
|
||||||
enum, union
|
enum, union
|
||||||
@@ -56,6 +57,10 @@ TokenKind :: enum {
|
|||||||
plus, minus
|
plus, minus
|
||||||
star, slash
|
star, slash
|
||||||
|
|
||||||
|
# bitwise
|
||||||
|
tilde
|
||||||
|
shift_left, shift_right, shift_left_saturating
|
||||||
|
|
||||||
range, range_inclusive, ellipsis
|
range, range_inclusive, ellipsis
|
||||||
at, ampersand, caret, dollar
|
at, ampersand, caret, dollar
|
||||||
question, semicolon
|
question, semicolon
|
||||||
@@ -67,6 +72,8 @@ TokenKind :: enum {
|
|||||||
# assignment & arithmetic
|
# assignment & arithmetic
|
||||||
plus_equal, minus_equal
|
plus_equal, minus_equal
|
||||||
star_equal, slash_equal
|
star_equal, slash_equal
|
||||||
|
ampersand_equal, pipe_equal, xor_equal
|
||||||
|
shift_left_equal, shift_right_equal, shift_left_saturating_equal
|
||||||
|
|
||||||
# delimiters
|
# delimiters
|
||||||
open_paren, close_paren
|
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)
|
operand :: try load(state, instruction.left)
|
||||||
try io.print(" %r{} = xor i1 %r{}, true\n", output, {result, operand})
|
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: {
|
.add, .subtract, .multiply, .divide: {
|
||||||
left :: try load(state, instruction.left)
|
left :: try load(state, instruction.left)
|
||||||
right :: try load(state, instruction.right)
|
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)
|
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.
|
#! compares the complete sentinel value, including nested aggregate fields.
|
||||||
@hide:file
|
@hide:file
|
||||||
sentinel_difference proc(state @mut State, type Type, actual, expected usize) usize ! io.WriteError {
|
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 bytes_constant(output, enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?, false)
|
||||||
try io.write_all(output, "\n")
|
try io.write_all(output, "\n")
|
||||||
}
|
}
|
||||||
memory_codes [7]diagnostics.Code :: [
|
memory_codes [_]diagnostics.Code :: [
|
||||||
.null_unwrap, .invalid_slice_bounds, .sentinel_mismatch,
|
.null_unwrap, .invalid_slice_bounds, .sentinel_mismatch,
|
||||||
.overlapping_memory_copy, .unsupported_foreign_call, .invalid_pointer_access,
|
.overlapping_memory_copy, .unsupported_foreign_call, .invalid_pointer_access,
|
||||||
.division_by_zero,
|
.division_by_zero,
|
||||||
|
.invalid_shift_count,
|
||||||
]
|
]
|
||||||
for (memory_codes) |code| {
|
for (memory_codes) |code| {
|
||||||
try io.print("@memory_{s} = private constant ", output, {memory_symbol(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"
|
.unsupported_foreign_call: "unsupported_foreign_call"
|
||||||
.invalid_pointer_access: "invalid_pointer_access"
|
.invalid_pointer_access: "invalid_pointer_access"
|
||||||
.division_by_zero: "division_by_zero"
|
.division_by_zero: "division_by_zero"
|
||||||
|
.invalid_shift_count: "invalid_shift_count"
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-13
@@ -927,6 +927,36 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
}
|
}
|
||||||
.assignment_stmt: {
|
.assignment_stmt: {
|
||||||
assignment :: ast.assignment_stmt(tree, id)
|
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 (
|
if (
|
||||||
ast.expr_kind(tree, assignment.target) == .index
|
ast.expr_kind(tree, assignment.target) == .index
|
||||||
or ast.expr_kind(tree, assignment.target) == .unary
|
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
|
@hide:file
|
||||||
lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
expression :: ast.binary_expr(state.tree, id)
|
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 {
|
op ir.Op :: match expression.operator {
|
||||||
.negate: .negate
|
.negate: .negate
|
||||||
.logical_not: .logical_not
|
.logical_not: .logical_not
|
||||||
|
.bit_not: .bit_not
|
||||||
.address_of, .dereference, .unwrap: unreachable
|
.address_of, .dereference, .unwrap: unreachable
|
||||||
}
|
}
|
||||||
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
|
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,
|
array_type = try array_shape(state, pointer.element), span = span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
op ir.Op :: match expression.operator {
|
op :: binary_op(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
|
|
||||||
}
|
|
||||||
return try emit(state, ir.Instruction{ op = op, type = type, left = left, right = right, span = span })
|
return try emit(state, ir.Instruction{ op = op, type = type, left = left, right = right, span = span })
|
||||||
}
|
}
|
||||||
.call: {
|
.call: {
|
||||||
|
|||||||
+93
-31
@@ -30,7 +30,7 @@ State :: struct {
|
|||||||
next_token usize
|
next_token usize
|
||||||
paren_depth usize = 0
|
paren_depth usize = 0
|
||||||
allow_record_literals bool = true
|
allow_record_literals bool = true
|
||||||
allow_sum_types bool = true
|
allow_bit_or bool = true
|
||||||
match_arm_body bool = false
|
match_arm_body bool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,16 +254,16 @@ parse_block proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
open :: try consume(state, .open_curly)
|
open :: try consume(state, .open_curly)
|
||||||
previous_depth :: state.paren_depth
|
previous_depth :: state.paren_depth
|
||||||
previous_record_literals :: state.allow_record_literals
|
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
|
previous_match_arm_body :: state.match_arm_body
|
||||||
state.paren_depth = 0
|
state.paren_depth = 0
|
||||||
state.allow_record_literals = true
|
state.allow_record_literals = true
|
||||||
state.allow_sum_types = true
|
state.allow_bit_or = true
|
||||||
state.match_arm_body = false
|
state.match_arm_body = false
|
||||||
defer {
|
defer {
|
||||||
state.paren_depth = previous_depth
|
state.paren_depth = previous_depth
|
||||||
state.allow_record_literals = previous_record_literals
|
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
|
state.match_arm_body = previous_match_arm_body
|
||||||
}
|
}
|
||||||
statements arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
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)
|
.break, .continue: return try parse_loop_control(state, allow_else)
|
||||||
.defer, .errdefer: return try parse_cleanup(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))
|
.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: {
|
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star, .question, .null, .undefined, .try, .dollar: {
|
||||||
expression :: try parse_expr(state)
|
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)
|
return try ast.add_expression_stmt(state.builder, expression)
|
||||||
}
|
}
|
||||||
else: return .unexpected_token
|
else: return .unexpected_token
|
||||||
@@ -431,8 +431,9 @@ parse_loop_control proc(state @mut State, allow_else bool) ast.NodeId ! ParseErr
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_assignment proc(state @mut State, target ast.NodeId, allow_else bool) ast.NodeId ! ParseError {
|
parse_assignment proc(state @mut State, target ast.NodeId, allow_else bool) ast.NodeId ! ParseError {
|
||||||
_ = try consume(state, .equal)
|
operator :: assignment_operator(current(state).kind)
|
||||||
value :: try parse_expr(state)
|
state.next_token += 1
|
||||||
|
value := try parse_expr(state)
|
||||||
if (
|
if (
|
||||||
!next_is(state, .newline)
|
!next_is(state, .newline)
|
||||||
and !next_is(state, .close_curly)
|
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 !(allow_else and next_is(state, .else))
|
||||||
and !(state.match_arm_body and next_is(state, .comma))
|
and !(state.match_arm_body and next_is(state, .comma))
|
||||||
) return .unexpected_token
|
) return .unexpected_token
|
||||||
end :: state.tokens[state.next_token - 1].span.end
|
span :: ast.Span{
|
||||||
return try ast.add_assignment_stmt(
|
|
||||||
state.builder,
|
|
||||||
ast.Span{
|
|
||||||
start = ast.builder_span(state.builder, target).start,
|
start = ast.builder_span(state.builder, target).start,
|
||||||
end = end,
|
end = state.tokens[state.next_token - 1].span.end,
|
||||||
source = state.builder.source_id,
|
source = state.builder.source_id,
|
||||||
},
|
}
|
||||||
target,
|
if (operator) |binary| value = try ast.add_binary(state.builder, span, binary, target, value)
|
||||||
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
|
@hide:file
|
||||||
@@ -657,13 +656,13 @@ parse_for proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
|||||||
@hide:file
|
@hide:file
|
||||||
parse_guard_expr proc(state @mut State) ast.NodeId ! ParseError {
|
parse_guard_expr proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
previous :: state.allow_record_literals
|
previous :: state.allow_record_literals
|
||||||
previous_sum_types :: state.allow_sum_types
|
previous_bit_or :: state.allow_bit_or
|
||||||
state.allow_record_literals = false
|
state.allow_record_literals = false
|
||||||
# capture pipes stay boundaries even when an index/call permits record literals.
|
# top-level header pipes delimit captures; nested expressions may use bitwise OR.
|
||||||
state.allow_sum_types = false
|
state.allow_bit_or = false
|
||||||
defer {
|
defer {
|
||||||
state.allow_record_literals = previous
|
state.allow_record_literals = previous
|
||||||
state.allow_sum_types = previous_sum_types
|
state.allow_bit_or = previous_bit_or
|
||||||
}
|
}
|
||||||
return try parse_expr(state)
|
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, .for) and !next_is(state, .inline)
|
||||||
and !next_is(state, .break) and !next_is(state, .continue)
|
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
|
state.next_token += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -952,7 +951,7 @@ starts_type proc(kind lexer.TokenKind) bool {
|
|||||||
member_name proc(kind lexer.TokenKind) bool {
|
member_name proc(kind lexer.TokenKind) bool {
|
||||||
return match kind {
|
return match kind {
|
||||||
.ident, .if, .else, .for, .inline, .while, .proc, .return, .yield, .break, .continue,
|
.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
|
.alias, .match, .import, .test, .c_func, .null, .undefined, .try, .catch, .defer, .errdefer: true
|
||||||
else: false
|
else: false
|
||||||
}
|
}
|
||||||
@@ -1193,7 +1192,7 @@ parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
body = try parse_arm(state, false)
|
body = try parse_arm(state, false)
|
||||||
} else {
|
} else {
|
||||||
value :: try parse_expr(state)
|
value :: try parse_expr(state)
|
||||||
if next_is(state, .equal) {
|
if (is_assignment_operator(current(state).kind)) {
|
||||||
statement :: try parse_assignment(state, value, false)
|
statement :: try parse_assignment(state, value, false)
|
||||||
statements [1]ast.NodeId :: [statement]
|
statements [1]ast.NodeId :: [statement]
|
||||||
body = try ast.add_block(state.builder, ast.builder_span(state.builder, statement), statements[..])
|
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 {
|
operator ast.UnaryOperator :: match token.kind {
|
||||||
.bang: .logical_not
|
.bang: .logical_not
|
||||||
|
.tilde: .bit_not
|
||||||
.ampersand: .address_of
|
.ampersand: .address_of
|
||||||
else: .negate
|
else: .negate
|
||||||
}
|
}
|
||||||
@@ -1477,11 +1477,10 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
|||||||
left_grouped = false
|
left_grouped = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (token.kind == .bang or (token.kind == .pipe and state.allow_sum_types)) {
|
if (token.kind == .bang) {
|
||||||
power :: if (token.kind == .bang) BindingPowerScalar(1) else BindingPowerScalar(2)
|
if (BindingPowerScalar(1) < min_bp) break
|
||||||
if (power < min_bp) break
|
|
||||||
state.next_token += 1
|
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{
|
left = try ast.add_binary_type(state.builder, ast.Span{
|
||||||
start = state.tokens[expression_start].span.start,
|
start = state.tokens[expression_start].span.start,
|
||||||
end = state.tokens[state.next_token - 1].span.end,
|
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
|
left_grouped = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if (token.kind == .pipe and !state.allow_bit_or) break
|
||||||
if (infix_binding_power(token.kind)) |power| {
|
if (infix_binding_power(token.kind)) |power| {
|
||||||
if (power.left < min_bp) break
|
if (power.left < min_bp) break
|
||||||
state.next_token += 1
|
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 {
|
parse_call proc(state @mut State, callee ast.NodeId, start u32, builtin bool) ast.NodeId ! ParseError {
|
||||||
_ = try consume(state, .open_paren)
|
_ = try consume(state, .open_paren)
|
||||||
previous :: state.allow_record_literals
|
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
|
state.allow_record_literals = true
|
||||||
defer state.allow_record_literals = previous
|
defer state.allow_record_literals = previous
|
||||||
state.paren_depth += 1
|
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 {
|
parse_index proc(state @mut State, base ast.NodeId, start u32) ast.NodeId ! ParseError {
|
||||||
_ = try consume(state, .open_bracket)
|
_ = try consume(state, .open_bracket)
|
||||||
previous :: state.allow_record_literals
|
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
|
state.allow_record_literals = true
|
||||||
defer state.allow_record_literals = previous
|
defer state.allow_record_literals = previous
|
||||||
first ?ast.NodeId := null
|
first ?ast.NodeId := null
|
||||||
@@ -1621,6 +1627,22 @@ parse_index proc(state @mut State, base ast.NodeId, start u32) ast.NodeId ! Pars
|
|||||||
}, base, first?)
|
}, 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
|
@hide:file
|
||||||
array_starts_type proc(state @State) bool {
|
array_starts_type proc(state @State) bool {
|
||||||
depth usize := 0
|
depth usize := 0
|
||||||
@@ -1650,6 +1672,9 @@ array_starts_type proc(state @State) bool {
|
|||||||
parse_array_literal proc(state @mut State) ast.NodeId ! ParseError {
|
parse_array_literal proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
open :: try consume(state, .open_bracket)
|
open :: try consume(state, .open_bracket)
|
||||||
previous :: state.allow_record_literals
|
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
|
state.allow_record_literals = true
|
||||||
defer state.allow_record_literals = previous
|
defer state.allow_record_literals = previous
|
||||||
elements arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
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 (depth != 0) continue
|
||||||
if (kind == .double_colon or kind == .colon_equal) return true
|
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
|
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
|
and !(index + 1 < state.tokens.len and state.tokens[index + 1].kind == .equal)) return true
|
||||||
if (depth == 1) {
|
if (depth == 1) {
|
||||||
if (kind == .double_colon or kind == .colon_equal) return true
|
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
|
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)) {
|
if (previous != .dot and !(index + 1 < state.tokens.len and state.tokens[index + 1].kind == .equal)) {
|
||||||
match kind {
|
match kind {
|
||||||
@@ -1777,6 +1803,9 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
_ = try consume(state, .open_paren)
|
_ = try consume(state, .open_paren)
|
||||||
previous :: state.allow_record_literals
|
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
|
state.allow_record_literals = true
|
||||||
defer state.allow_record_literals = previous
|
defer state.allow_record_literals = previous
|
||||||
state.paren_depth += 1
|
state.paren_depth += 1
|
||||||
@@ -1812,6 +1841,9 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
.open_paren: {
|
.open_paren: {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
previous :: state.allow_record_literals
|
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
|
state.allow_record_literals = true
|
||||||
defer state.allow_record_literals = previous
|
defer state.allow_record_literals = previous
|
||||||
state.paren_depth += 1
|
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.
|
# a missing operand must not consume the declaration or assignment on the next line.
|
||||||
if (
|
if (
|
||||||
named_binding_starts(state)
|
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
|
) state.next_token = start
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2083,7 +2115,7 @@ consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! Syn
|
|||||||
@hide:file
|
@hide:file
|
||||||
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
||||||
return match kind {
|
return match kind {
|
||||||
.minus, .bang, .ampersand, .try: BindingPowerScalar(50)
|
.minus, .bang, .tilde, .ampersand, .try: BindingPowerScalar(50)
|
||||||
else: null
|
else: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2095,6 +2127,8 @@ infix_binding_power proc(kind lexer.TokenKind) ?BindingPower {
|
|||||||
.and: BindingPower{ left = BindingPowerScalar(4), right = BindingPowerScalar(5) }
|
.and: BindingPower{ left = BindingPowerScalar(4), right = BindingPowerScalar(5) }
|
||||||
.equal_equal, .not_equal: BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) }
|
.equal_equal, .not_equal: BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) }
|
||||||
.less, .less_equal, .greater, .greater_equal: BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) }
|
.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) }
|
.plus, .minus: BindingPower{ left = BindingPowerScalar(30), right = BindingPowerScalar(31) }
|
||||||
.star, .slash: BindingPower{ left = BindingPowerScalar(40), right = BindingPowerScalar(41) }
|
.star, .slash: BindingPower{ left = BindingPowerScalar(40), right = BindingPowerScalar(41) }
|
||||||
else: null
|
else: null
|
||||||
@@ -2110,6 +2144,12 @@ binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator {
|
|||||||
.minus: .subtract
|
.minus: .subtract
|
||||||
.star: .multiply
|
.star: .multiply
|
||||||
.slash: .divide
|
.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
|
.equal_equal: .equal
|
||||||
.not_equal: .not_equal
|
.not_equal: .not_equal
|
||||||
.less: .less
|
.less: .less
|
||||||
@@ -2119,3 +2159,25 @@ binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator {
|
|||||||
else: unreachable
|
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)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
`main proc() void {
|
`main proc() void {
|
||||||
` bad [~]i64 :: [~, 2]
|
` bad [%]i64 :: [%, 2]
|
||||||
` while (items[0) { break }
|
` while (items[0) { break }
|
||||||
` for items[0 |i| { continue }
|
` for items[0 |i| { continue }
|
||||||
` unfinished :: [1,
|
` 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.comptime_expr, ast.expr_kind(&tree, constant))
|
||||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, ast.comptime_operand(&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) {
|
match ast.expr_kind(tree, id) {
|
||||||
.unary: {
|
.unary: {
|
||||||
expression :: ast.unary_expr(tree, id)
|
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: {
|
.binary: {
|
||||||
expression :: ast.binary_expr(tree, id)
|
expression :: ast.binary_expr(tree, id)
|
||||||
@@ -464,7 +464,7 @@ check_elaborated proc(
|
|||||||
match ast.expr_kind(tree, id) {
|
match ast.expr_kind(tree, id) {
|
||||||
.unary: {
|
.unary: {
|
||||||
expression :: ast.unary_expr(tree, id)
|
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: {
|
.binary: {
|
||||||
expression :: ast.binary_expr(tree, id)
|
expression :: ast.binary_expr(tree, id)
|
||||||
@@ -473,7 +473,11 @@ check_elaborated proc(
|
|||||||
if (!bool_equality(&state, id)) try comparison_operands(&state, id)
|
if (!bool_equality(&state, id)) try comparison_operands(&state, id)
|
||||||
} else {
|
} else {
|
||||||
try numeric_operand(&state, id, expression.left)
|
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) {
|
if (expression.operator == .divide) {
|
||||||
try impose(&state, id, .float, .integer_division, expression.span)
|
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: {
|
.unary: {
|
||||||
expression :: ast.unary_expr(tree, id)
|
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) {
|
if (expression.operator == .logical_not) {
|
||||||
state.cells[usize(id)].type = Type{ kind = .bool }
|
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
|
state.cells[usize(id)].numeric = true
|
||||||
}
|
}
|
||||||
operand :: expression.operand
|
operand :: expression.operand
|
||||||
@@ -1570,6 +1574,27 @@ logical proc(operator ast.BinaryOperator) bool {
|
|||||||
return operator == .logical_and or operator == .logical_or
|
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
|
@hide:file
|
||||||
pointer_addition proc(state @mut State, id ast.NodeId) bool {
|
pointer_addition proc(state @mut State, id ast.NodeId) bool {
|
||||||
expression :: ast.binary_expr(state.tree, id)
|
expression :: ast.binary_expr(state.tree, id)
|
||||||
@@ -2153,7 +2178,9 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
operand :: state.types[usize(expression.operand)]
|
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: {
|
.binary: {
|
||||||
expression :: ast.binary_expr(tree, id)
|
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)]
|
left :: state.types[usize(expression.left)]
|
||||||
right :: state.types[usize(expression.right)]
|
right :: state.types[usize(expression.right)]
|
||||||
if (left.kind == .invalid or right.kind == .invalid) return
|
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 (pointer_addition(state, id)) {
|
||||||
if (right.kind != .usize or right.nominal != ast.NodeId(maxval!(u32))) try fail(state, id, .invalid_operand, expression.span)
|
if (right.kind != .usize or right.nominal != ast.NodeId(maxval!(u32))) try fail(state, id, .invalid_operand, expression.span)
|
||||||
return
|
return
|
||||||
@@ -2378,6 +2420,53 @@ constant_integer proc(state @mut State, id ast.NodeId) ?ConstantInteger {
|
|||||||
return evaluate_integer(state, id, false)
|
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
|
@hide:file
|
||||||
evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?ConstantInteger {
|
evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?ConstantInteger {
|
||||||
if (state.cells[usize(id)].constant_visiting) return null
|
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: {
|
.unary: {
|
||||||
view :: ast.unary_expr(state.tree, id)
|
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 (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 ConstantInteger{ magnitude = value.magnitude, negative = value.magnitude != 0 and !value.negative }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@@ -2443,6 +2539,7 @@ evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?Consta
|
|||||||
right := ConstantInteger{ magnitude = 0 }
|
right := ConstantInteger{ magnitude = 0 }
|
||||||
if (evaluate_integer(state, view.left, allow_local)) |value| left = value else return null
|
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 (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 (view.operator == .multiply) {
|
||||||
if (right.magnitude != 0 and left.magnitude > divtrunc!(maxval!(u64), right.magnitude)) return null
|
if (right.magnitude != 0 and left.magnitude > divtrunc!(maxval!(u64), right.magnitude)) return null
|
||||||
magnitude :: left.magnitude * right.magnitude
|
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)
|
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)
|
view :: ast.binary_expr(state.tree, producer)
|
||||||
left :: bound_family(state, view.left)
|
left :: bound_family(state, view.left)
|
||||||
|
if (shift(view.operator)) return left
|
||||||
right :: bound_family(state, view.right)
|
right :: bound_family(state, view.right)
|
||||||
if (left == .float or right == .float) return .float
|
if (left == .float or right == .float) return .float
|
||||||
if (integer_kind(left)) return left
|
if (integer_kind(left)) return left
|
||||||
|
|||||||
@@ -718,7 +718,7 @@ resolves_every_array_child_error_and_preserves_lexer_diagnostics test {
|
|||||||
` return
|
` return
|
||||||
` data [missing_count]MissingElement :: [absent, missing_element]
|
` data [missing_count]MissingElement :: [absent, missing_element]
|
||||||
` (missing_base[missing_index])[missing_outer] = missing_rhs
|
` (missing_base[missing_index])[missing_outer] = missing_rhs
|
||||||
` malformed [~]i64 :: [~]
|
` malformed [%]i64 :: [%]
|
||||||
`}
|
`}
|
||||||
scan_state := lexer.init(mem.c_allocator)
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ elaborate proc(
|
|||||||
if (class == .expression
|
if (class == .expression
|
||||||
and (ast.expr_kind(&original, id) == .comptime_expr or ast.expr_kind(&original, id) == .block_expr))
|
and (ast.expr_kind(&original, id) == .comptime_expr or ast.expr_kind(&original, id) == .block_expr))
|
||||||
needed = true
|
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
|
if (class == .statement and ast.stmt_kind(&original, id) == .for_stmt
|
||||||
and ast.for_stmt(&original, id).is_inline) needed = true
|
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
|
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) {
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .binary) {
|
||||||
view :: ast.binary_expr(&state.tree, id)
|
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 (
|
if (
|
||||||
(view.operator == .equal or view.operator == .not_equal)
|
(view.operator == .equal or view.operator == .not_equal)
|
||||||
and branch_constant(state, view.left, 0) and branch_constant(state, view.right, 0)
|
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: {
|
.unary: {
|
||||||
view :: ast.unary_expr(&state.tree, id)
|
view :: ast.unary_expr(&state.tree, id)
|
||||||
return (
|
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)
|
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)
|
kind :: ast.expr_kind(&state.tree, id)
|
||||||
if (kind == .builtin_call)
|
if (kind == .builtin_call)
|
||||||
return mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name), "some")
|
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
|
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) {
|
if (target != assignment.target) {
|
||||||
normalized :: try ast.add_assignment_stmt(state.builder,
|
normalized :: try ast.add_assignment_stmt(state.builder,
|
||||||
ast.node_span(&state.tree, statement), target, assignment.value)
|
ast.node_span(&state.tree, statement), target, assignment.value)
|
||||||
|
if (assignment.compound) ast.set_assignment_compound(state.builder, normalized)
|
||||||
try refresh(state)
|
try refresh(state)
|
||||||
# synthesized places still need local substitution in snapshots.
|
# synthesized places still need local substitution in snapshots.
|
||||||
previous :: execution.mapping.len
|
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))
|
.alias_expr: return try evaluate(state, ast.type_operand(&state.tree, id))
|
||||||
.unary: {
|
.unary: {
|
||||||
view :: ast.unary_expr(&state.tree, id)
|
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)
|
value := try evaluate(state, view.operand)
|
||||||
if (view.operator == .negate and value.kind == .integer) value.negative = value.magnitude != 0 and !value.negative
|
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)
|
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 {
|
evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
|
||||||
view :: ast.binary_expr(&state.tree, id)
|
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 == .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)
|
left :: try evaluate(state, view.left)
|
||||||
if (left.kind == .invalid) return Value{}
|
if (left.kind == .invalid) return Value{}
|
||||||
if (left.kind == .boolean) {
|
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 == .less_equal or view.operator == .greater or view.operator == .greater_equal
|
||||||
or view.operator == .logical_and or view.operator == .logical_or
|
or view.operator == .logical_and or view.operator == .logical_or
|
||||||
) return try builtin(state, id, .bool)
|
) 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)
|
return try expression_type(state, view.left, depth + 1)
|
||||||
}
|
}
|
||||||
.selection: {
|
.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 == .literal_int or kind == .literal_float) return true
|
||||||
if (kind == .unary) {
|
if (kind == .unary) {
|
||||||
view :: ast.unary_expr(&state.tree, id)
|
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) {
|
if (kind == .binary) {
|
||||||
view :: ast.binary_expr(&state.tree, id)
|
view :: ast.binary_expr(&state.tree, id)
|
||||||
return (
|
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)
|
and flexible(state, view.left) and flexible(state, view.right)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+149
@@ -11,6 +11,135 @@ HONEY = ROOT / "build/honey"
|
|||||||
|
|
||||||
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
||||||
CASES = {
|
CASES = {
|
||||||
|
"distinct_bitwise_results_preserve_nominal_type": (
|
||||||
|
"""
|
||||||
|
Flags :: distinct u16
|
||||||
|
Signed :: distinct i8
|
||||||
|
exact proc(value Flags) Flags { return value }
|
||||||
|
main proc() [4]i16 {
|
||||||
|
flags Flags := 21
|
||||||
|
flags <<= u8(1)
|
||||||
|
flags xor= 3
|
||||||
|
flags &= 63
|
||||||
|
flags |= 2
|
||||||
|
signed Signed := -3
|
||||||
|
signed >>= u64(1)
|
||||||
|
return [i16(exact(flags)), i16(~Flags(65500)),
|
||||||
|
i16(signed), i16(Signed(-2) <<| 7)]
|
||||||
|
}
|
||||||
|
""", b"[43, 35, -2, -128]\n", None,
|
||||||
|
),
|
||||||
|
"compound_updates_evaluate_places_once_before_rhs": (
|
||||||
|
"""
|
||||||
|
Box :: struct { values [2]mut i16 }
|
||||||
|
place proc(box @mut Box, trace @mut i16) @mut i16 {
|
||||||
|
trace^ = trace^ * 10 + 1
|
||||||
|
return &box.values[0]
|
||||||
|
}
|
||||||
|
rhs proc(box @mut Box, trace @mut i16) i16 {
|
||||||
|
trace^ = trace^ * 10 + 2
|
||||||
|
box.values[0] = 100
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
index proc(trace @mut i16) usize {
|
||||||
|
trace^ = trace^ * 10 + 3
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
main proc() [4]i16 {
|
||||||
|
box Box := { values = [40, 6] }
|
||||||
|
trace i16 := 0
|
||||||
|
place(&box, &trace)^ += rhs(&box, &trace)
|
||||||
|
box.values[index(&trace)] *= 7
|
||||||
|
count i16 := 0
|
||||||
|
for (0..3) |_| { defer count += 1 }
|
||||||
|
return [box.values[0], box.values[1], trace, count]
|
||||||
|
}
|
||||||
|
""", b"[42, 42, 123, 3]\n", None,
|
||||||
|
),
|
||||||
|
"compound_bitwise_and_float_updates": (
|
||||||
|
"""
|
||||||
|
main proc() [4]i16 {
|
||||||
|
value u8 := 255
|
||||||
|
value &= 15
|
||||||
|
value xor= 3
|
||||||
|
value |= 1
|
||||||
|
value <<= 1
|
||||||
|
value >>= 1
|
||||||
|
value <<|= u64(1000)
|
||||||
|
number i16 := 20
|
||||||
|
number += 3
|
||||||
|
number -= 2
|
||||||
|
number *= 2
|
||||||
|
real f64 := 84
|
||||||
|
real /= 2
|
||||||
|
return [i16(value), number, i16(real), i16(~u8(213))]
|
||||||
|
}
|
||||||
|
""", b"[255, 42, 42, 42]\n", None,
|
||||||
|
),
|
||||||
|
"bitwise_precedence_and_type_composition": (
|
||||||
|
"""
|
||||||
|
A :: enum { first }
|
||||||
|
B :: enum { second }
|
||||||
|
Error :: A | B
|
||||||
|
fail proc() i16 ! Error { return .first }
|
||||||
|
main proc() [3]i16 {
|
||||||
|
return [i16(u8(1) | 2 xor 3 & 6), i16(u8(3) << (1 + 1)),
|
||||||
|
fail() catch |error| match error { .first: 42, .second: 0 }]
|
||||||
|
}
|
||||||
|
""", b"[0, 12, 42]\n", None,
|
||||||
|
),
|
||||||
|
"bitwise_constants_and_type_producer_updates": (
|
||||||
|
"""
|
||||||
|
Fixed proc() type {
|
||||||
|
counts [2]mut usize := [1, 2]
|
||||||
|
counts[0] <<= 2
|
||||||
|
counts[1] |= 1
|
||||||
|
total usize := counts[0]
|
||||||
|
total += counts[1]
|
||||||
|
return struct { values [total]i16, answer i16 = i16(~u8(213)) }
|
||||||
|
}
|
||||||
|
compute proc() u8 {
|
||||||
|
value u8 := 21
|
||||||
|
value <<= 1
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
choose proc($value u8) i16 {
|
||||||
|
if ((value & 3) == 2) return 42
|
||||||
|
else return missing_member(value)
|
||||||
|
}
|
||||||
|
main proc() [4]i16 {
|
||||||
|
generated Fixed() :: { values = [0, 0, 0, 0, 0, 0, 0] }
|
||||||
|
array [(usize(1) << 2)]i16 :: [0, 0, 0, 0]
|
||||||
|
return [generated.answer, i16($(compute())), choose($(u8(21) << 1)),
|
||||||
|
i16(generated.values.len + array.len)]
|
||||||
|
}
|
||||||
|
missing_member proc(value u8) bool { return true }
|
||||||
|
""", b"[42, 42, 42, 11]\n", None,
|
||||||
|
),
|
||||||
|
"compound_signed_overflow": (
|
||||||
|
"main proc() i8 { value i8 := 127\nvalue += 1\nreturn value }",
|
||||||
|
b"", b"integer arithmetic overflow",
|
||||||
|
),
|
||||||
|
"compound_readonly_place": (
|
||||||
|
"main proc() i16 { values [1]i16 := [40]\nvalues[0] += 2\nreturn 0 }",
|
||||||
|
b"", b"indexed assignment requires a writable array element",
|
||||||
|
),
|
||||||
|
"bitwise_float_rejected": (
|
||||||
|
"main proc() f64 { return f64(1) & 2 }",
|
||||||
|
b"", b"operator requires compatible operand types",
|
||||||
|
),
|
||||||
|
"shift_signed_count_rejected": (
|
||||||
|
"main proc() i16 { return i16(1) << i16(2) }",
|
||||||
|
b"", b"operator requires compatible operand types",
|
||||||
|
),
|
||||||
|
"shift_known_count_rejected_even_untaken": (
|
||||||
|
"main proc() i16 { if (false) { _ = u8(1) << 8 }\nreturn 42 }",
|
||||||
|
b"42\n", b"shift count must be smaller than the integer bit width",
|
||||||
|
),
|
||||||
|
"shift_runtime_count_traps": (
|
||||||
|
"shift proc(count u64) u8 { return u8(1) << count }\nmain proc() u8 { return shift(256) }",
|
||||||
|
b"", b"shift count must be smaller than the integer bit width",
|
||||||
|
),
|
||||||
"yielding_blocks_nest_and_copy_before_scoped_cleanup": (
|
"yielding_blocks_nest_and_copy_before_scoped_cleanup": (
|
||||||
"""
|
"""
|
||||||
Item :: struct { values [1]mut i16 }
|
Item :: struct { values [1]mut i16 }
|
||||||
@@ -7304,6 +7433,26 @@ for integer_type, bits, signed in (
|
|||||||
):
|
):
|
||||||
minimum = -(1 << (bits - 1)) if signed else 0
|
minimum = -(1 << (bits - 1)) if signed else 0
|
||||||
maximum = (1 << (bits - int(signed))) - 1
|
maximum = (1 << (bits - int(signed))) - 1
|
||||||
|
CASES[f"{integer_type}_bitwise_boundaries"] = (
|
||||||
|
f"""
|
||||||
|
shift proc(value {integer_type}, count u64) [4]{integer_type} {{
|
||||||
|
return [value << count, value >> count, value <<| count, ~value]
|
||||||
|
}}
|
||||||
|
main proc() [8]{integer_type} {{
|
||||||
|
high :: shift(maxval!({integer_type}), {bits - 1})
|
||||||
|
huge :: shift_zero({integer_type}(0), maxval!(u64))
|
||||||
|
return [high[0], high[1], high[2], high[3],
|
||||||
|
{integer_type}(1) <<| maxval!(u64), huge,
|
||||||
|
minval!({integer_type}) <<| maxval!(u64),
|
||||||
|
minval!({integer_type}) >> u8({bits - 1})]
|
||||||
|
}}
|
||||||
|
shift_zero proc(value {integer_type}, count u64) {integer_type} {{ return value <<| count }}
|
||||||
|
""",
|
||||||
|
(f"[{- (1 << (bits - 1)) if signed else 1 << (bits - 1)}, "
|
||||||
|
f"{0 if signed else 1}, {maximum}, {minimum}, {maximum}, 0, {minimum}, "
|
||||||
|
f"{-1 if signed else 0}]\n").encode(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
CASES[f"{integer_type}_limits_and_truncating_division"] = (
|
CASES[f"{integer_type}_limits_and_truncating_division"] = (
|
||||||
f"""
|
f"""
|
||||||
identity proc(value {integer_type}) {integer_type} {{ return value }}
|
identity proc(value {integer_type}) {integer_type} {{ return value }}
|
||||||
|
|||||||
Reference in New Issue
Block a user