Add arrays: literal, indexing, index assignment, and len() - #1
Open
erichanwang wants to merge 1 commit into
Open
Add arrays: literal, indexing, index assignment, and len()#1erichanwang wants to merge 1 commit into
erichanwang wants to merge 1 commit into
Conversation
The AST already had ARRAY/INDEX/INDEXSET nodes and a parser for them
sitting unused - neither the interpreter nor the codegen implemented
them. This wires both up and, more importantly, pins down the one
thing the parser left open: what happens on a bad index.
Semantics, chosen so both backends can agree on them exactly:
- An index must be a Number; truncates toward zero via (long), same
cast on both sides.
- Reading with an out-of-bounds, negative-after-truncation, or
non-numeric index, or indexing a non-array, yields an ERROR value
(same style as the existing division-by-zero and undefined-name
errors) rather than crashing or reading garbage.
- Writing through any of those same invalid conditions is a silent
no-op - but the index and value expressions still get evaluated,
so a function call used as either one still runs and can still
print. Getting this half-evaluated case wrong is exactly the kind
of thing edge_cases.lang was written to catch elsewhere.
Arrays are reference types: a Value copy (assignment, a call argument,
an element of another array) shares the backing storage, matching
what the compiled side gets for free from passing a Value* around.
The interpreter gets the same aliasing via shared_ptr<vector<Value>>
rather than deep-copying on every assignment.
len() is a reserved builtin intercepted ahead of the normal function
call path in both the interpreter and genCall, not a real function -
a program cannot declare its own len.
Indexing stays single-level (grid[0][1] does not parse as chained
indexing); syntax.txt documents assigning the inner array to a
temporary as the workaround.
Tests: arrays.lang covers literal/index/len/aliasing/functions/nested
arrays; array_edge_cases.lang specifically hunts for interpreter vs
codegen drift on bad indices, non-arrays, and half-evaluated invalid
writes. All 15 .lang files pass run_tests.sh, and g++ -Wall -Wextra
is clean.
Did not add the for-loop stretch goal this round - arrays alone touch
every stage (lexer already had the tokens, parser already had the
nodes, both Value representations, both codegens, runtime.c) and
deserved the full edge-case pass rather than splitting attention.
benchmark.sh on bench/workload.lang (no arrays in it, so unaffected by
this change) still lands at roughly 3x compiled vs interpreted -
2.9x-3.9x across repeated runs, consistent with the noise band before
this change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The AST already had ARRAY/INDEX/INDEXSET node types and a parser that built them, but neither the interpreter nor the codegen actually did anything with those nodes yet. This fills that gap end to end and, more importantly, pins down the one thing the parser left undecided: what happens on a bad index.
Why this was needed: arrays are the biggest remaining feature and the one that forces a heap object type through every stage - lexer, parser, both Value representations, both codegens, and runtime.c.
Semantics chosen (documented in syntax.txt), picked so the interpreter and the compiled backend can agree exactly:
(long)cast, the same operation on both sides.arr[i] = v) under any of those same invalid conditions is a silent no-op, but the index and value expressions are still evaluated - so a function call used as either one still runs and can still print. Getting this half-evaluated case wrong is exactly the kind of drift edge_cases.lang was written to catch elsewhere in the language.shared_ptr<vector<Value>>, matching what the compiled side gets for free from passing aValue*pointer around.len(arr)is a reserved builtin intercepted ahead of the normal function-call path in both backends - it is not a real function, so a program cannot declare its ownlen.grid[0][1]does not parse as chained indexing (pre-existing parser limitation). syntax.txt documents assigning the inner array to a temporary as the workaround.What I measured:
./run_tests.sh: all 15.langfiles pass (13 pre-existing plus two new array files), interpreter and compiled x86-64 output diffed identically on every one.g++ -O2 -std=c++17 -Wall -Wextra -o /tmp/axc compiler.cpp: clean, no warnings../benchmark.shonbench/workload.lang(which has no arrays, so it isolates whether this change touched the hot path): 2.9x-3.9x compiled vs interpreted across repeated runs, same noise band the project was already at before this change - arrays did not regress the benchmark.New test files, written in the same spirit as edge_cases.lang - hunting for backend drift rather than just proving the happy path:
arrays.lang: literal construction, indexing,len(), index assignment, aliasing across variable and function-argument boundaries, nested arrays via the documented temporary workaround, indexing by a computed (non-literal) expression, and the empty array.array_edge_cases.lang: out-of-bounds reads both directions, negative indices, non-integer index truncation, indexing a non-array, indexing/len()on a value that is itself an error (must propagate rather than being masked by an "index out of bounds" message), and out-of-bounds/invalid writes where the index or value expression is a function call that prints - to confirm the no-op still lets the side effect through.What a reviewer should check:
runtime.c'srt_array_get/rt_array_setandcompiler.cpp'sarrayIndex/execStmt'sINDEXSETcase check conditions in the same order (error propagation, then type checks, then bounds) - that ordering is what makes the two backends agree, not just coincidence.for-loop stretch goal in this pass; arrays alone touched every stage and I wanted the full edge-case pass on them rather than splitting attention.