🐐 Array slicing

This commit is contained in:
2026-04-21 22:53:40 +02:00
parent 559b1b100e
commit 378c28102c
13 changed files with 423 additions and 2 deletions

View File

@@ -121,6 +121,14 @@ Individual elements can be accessed by index using square brackets. Indexing is
> I
```
A sub-array can be extracted with `VSQVE` inside the index brackets. Both bounds are inclusive and 1-based:
![Array slicing](snippets/array_slice.png)
```
> [XX, XXX, XL]
```
### Dicts (TABVLA)
Dicts are key-value maps created with the `TABVLA` keyword and curly braces:

View File

@@ -847,6 +847,49 @@ class ArrayIndex(Node):
return vtable, lst[i - 1]
def _to_index_int(val):
if isinstance(val, ValInt):
return val.value()
if isinstance(val, ValFrac) and val.value().denominator == 1:
return val.value().numerator
raise CentvrionError("Array index must be a number")
class ArraySlice(Node):
def __init__(self, array, from_index, to_index) -> None:
self.array = array
self.from_index = from_index
self.to_index = to_index
def __eq__(self, other):
return (type(self) == type(other)
and self.array == other.array
and self.from_index == other.from_index
and self.to_index == other.to_index)
def __repr__(self) -> str:
return f"ArraySlice({self.array!r}, {self.from_index!r}, {self.to_index!r})"
def print(self):
return f"{self.array.print()}[{self.from_index.print()} VSQVE {self.to_index.print()}]"
def _eval(self, vtable):
vtable, array = self.array.eval(vtable)
vtable, from_val = self.from_index.eval(vtable)
vtable, to_val = self.to_index.eval(vtable)
if not isinstance(array, ValList):
raise CentvrionError("Cannot slice a non-array value")
from_int = _to_index_int(from_val)
to_int = _to_index_int(to_val)
lst = array.value()
if from_int < 1 or to_int > len(lst) or from_int > to_int:
raise CentvrionError(
f"Slice [{from_int} VSQVE {to_int}] out of range"
f" for array of length {len(lst)}"
)
return vtable, ValList(lst[from_int - 1 : to_int])
class SiStatement(Node):
def __init__(self, test, statements, else_part) -> None:
self.test = test

View File

@@ -2,7 +2,7 @@ from centvrion.errors import CentvrionError
from centvrion.ast_nodes import (
String, InterpolatedString, Numeral, Fractio, Bool, Nullus, ID,
BinOp, UnaryMinus, UnaryNot,
ArrayIndex, DataArray, DataRangeArray, DataDict,
ArrayIndex, ArraySlice, DataArray, DataRangeArray, DataDict,
BuiltIn, Invoca, Fvnctio,
num_to_int, frac_to_fraction,
)
@@ -120,6 +120,15 @@ def emit_expr(node, ctx):
tmp = ctx.fresh_tmp()
return arr_lines + idx_lines + [f"CentValue {tmp} = cent_list_index({arr_var}, {idx_var});"], tmp
if isinstance(node, ArraySlice):
arr_lines, arr_var = emit_expr(node.array, ctx)
lo_lines, lo_var = emit_expr(node.from_index, ctx)
hi_lines, hi_var = emit_expr(node.to_index, ctx)
tmp = ctx.fresh_tmp()
return arr_lines + lo_lines + hi_lines + [
f"CentValue {tmp} = cent_list_slice({arr_var}, {lo_var}, {hi_var});"
], tmp
if isinstance(node, DataArray):
lines = []
tmp = ctx.fresh_tmp()

View File

@@ -735,6 +735,22 @@ CentValue cent_list_index(CentValue lst, CentValue idx) {
return lst.lval.items[i - 1];
}
CentValue cent_list_slice(CentValue lst, CentValue lo, CentValue hi) {
if (lst.type != CENT_LIST)
cent_type_error("slice requires a list");
if (lo.type != CENT_INT || hi.type != CENT_INT)
cent_type_error("slice indices must be integers");
long from = lo.ival;
long to = hi.ival;
if (from < 1 || to > lst.lval.len || from > to)
cent_runtime_error("slice out of range");
int len = (int)(to - from + 1);
CentValue result = cent_list_new(len);
for (long j = from; j <= to; j++)
cent_list_push(&result, lst.lval.items[j - 1]);
return result;
}
void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v) {
if (lst->type == CENT_DICT) {
cent_dict_set(lst, idx, v);

View File

@@ -234,6 +234,7 @@ void cent_adivnge(CentValue path, CentValue content); /* ADIVNGE */
CentValue cent_list_new(int cap);
void cent_list_push(CentValue *lst, CentValue v);
CentValue cent_list_index(CentValue lst, CentValue idx); /* 1-based */
CentValue cent_list_slice(CentValue lst, CentValue lo, CentValue hi); /* 1-based, inclusive */
void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v);
/* ------------------------------------------------------------------ */

View File

@@ -326,6 +326,10 @@ class Parser():
def array_index(tokens):
return ast_nodes.ArrayIndex(tokens[0], tokens[2])
@self.pg.production('expression : expression SYMBOL_LBRACKET expression KEYWORD_VSQVE expression SYMBOL_RBRACKET', precedence='INDEX')
def array_slice(tokens):
return ast_nodes.ArraySlice(tokens[0], tokens[2], tokens[4])
# ids
@self.pg.production('ids : SYMBOL_LPARENS id_list')
def ids(tokens):

2
language/main.aux Normal file
View File

@@ -0,0 +1,2 @@
\relax
\gdef \@abspage@last{2}

269
language/main.log Normal file
View File

@@ -0,0 +1,269 @@
This is XeTeX, Version 3.141592653-2.6-0.999998 (TeX Live 2026/Arch Linux) (preloaded format=xelatex 2026.4.8) 21 APR 2026 22:51
entering extended mode
restricted \write18 enabled.
%&-line parsing enabled.
**main.tex
(./main.tex
LaTeX2e <2025-11-01>
L3 programming layer <2026-01-19>
(/usr/share/texmf-dist/tex/latex/base/article.cls
Document Class: article 2025/01/22 v1.4n Standard LaTeX document class
(/usr/share/texmf-dist/tex/latex/base/size10.clo
File: size10.clo 2025/01/22 v1.4n Standard LaTeX file (size option)
)
\c@part=\count271
\c@section=\count272
\c@subsection=\count273
\c@subsubsection=\count274
\c@paragraph=\count275
\c@subparagraph=\count276
\c@figure=\count277
\c@table=\count278
\abovecaptionskip=\skip49
\belowcaptionskip=\skip50
\bibindent=\dimen148
)
(/usr/share/texmf-dist/tex/latex/geometry/geometry.sty
Package: geometry 2020/01/02 v5.9 Page Geometry
(/usr/share/texmf-dist/tex/latex/graphics/keyval.sty
Package: keyval 2022/05/29 v1.15 key=value parser (DPC)
\KV@toks@=\toks17
)
(/usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
Package: ifvtex 2019/10/25 v1.7 ifvtex legacy package. Use iftex instead.
(/usr/share/texmf-dist/tex/generic/iftex/iftex.sty
Package: iftex 2024/12/12 v1.0g TeX engine tests
))
\Gm@cnth=\count279
\Gm@cntv=\count280
\c@Gm@tempcnt=\count281
\Gm@bindingoffset=\dimen149
\Gm@wd@mp=\dimen150
\Gm@odd@mp=\dimen151
\Gm@even@mp=\dimen152
\Gm@layoutwidth=\dimen153
\Gm@layoutheight=\dimen154
\Gm@layouthoffset=\dimen155
\Gm@layoutvoffset=\dimen156
\Gm@dimlist=\toks18
)
(/usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
(/usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
(/usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
Package: expl3 2026-01-19 L3 programming layer (loader)
(/usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
File: l3backend-xetex.def 2025-10-09 L3 backend support: XeTeX
\g__graphics_track_int=\count282
\g__pdfannot_backend_int=\count283
\g__pdfannot_backend_link_int=\count284
))
Package: xparse 2025-10-09 L3 Experimental document command parser
)
Package: fontspec 2025/09/29 v2.9g Font selection for XeLaTeX and LuaLaTeX
(/usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
Package: fontspec-xetex 2025/09/29 v2.9g Font selection for XeLaTeX and LuaLaTe
X
\l__fontspec_script_int=\count285
\l__fontspec_language_int=\count286
\l__fontspec_strnum_int=\count287
\l__fontspec_tmp_int=\count288
\l__fontspec_tmpa_int=\count289
\l__fontspec_tmpb_int=\count290
\l__fontspec_tmpc_int=\count291
\l__fontspec_em_int=\count292
\l__fontspec_emdef_int=\count293
\l__fontspec_strong_int=\count294
\l__fontspec_strongdef_int=\count295
\l__fontspec_tmpa_dim=\dimen157
\l__fontspec_tmpb_dim=\dimen158
\l__fontspec_tmpc_dim=\dimen159
(/usr/share/texmf-dist/tex/latex/base/fontenc.sty
Package: fontenc 2025/07/18 v2.1d Standard LaTeX package
)
(/usr/share/texmf-dist/tex/latex/fontspec/fontspec.cfg)))
Package fontspec Info:
(fontspec) Hurmit Nerd Font Mono scale = 0.7.
Package fontspec Info:
(fontspec) Hurmit Nerd Font Mono scale = 0.7.
Package fontspec Info:
(fontspec) Hurmit Nerd Font Mono/B scale = 0.7.
Package fontspec Info:
(fontspec) Hurmit Nerd Font Mono/I scale = 0.7.
Package fontspec Info:
(fontspec) Hurmit Nerd Font Mono/BI scale = 0.7.
Package fontspec Info:
(fontspec) Font family 'HurmitNerdFontMono(0)' created for font
(fontspec) 'Hurmit Nerd Font Mono' with options
(fontspec) [WordSpace={1,0,0},HyphenChar=None,PunctuationSpace=Word
Space,Scale=0.7].
(fontspec)
(fontspec) This font family consists of the following NFSS
(fontspec) series/shapes:
(fontspec)
(fontspec) - 'normal' (m/n) with NFSS spec.: <->s*[0.7]"Hurmit
(fontspec) Nerd Font Mono/OT:script=DFLT;language=dflt;"
(fontspec) - 'bold' (b/n) with NFSS spec.: <->s*[0.7]"Hurmit Nerd
(fontspec) Font Mono/B/OT:script=DFLT;language=dflt;"
(fontspec) - 'italic' (m/it) with NFSS spec.: <->s*[0.7]"Hurmit
(fontspec) Nerd Font Mono/I/OT:script=DFLT;language=dflt;"
(fontspec) - 'bold italic' (b/it) with NFSS spec.:
(fontspec) <->s*[0.7]"Hurmit Nerd Font
(fontspec) Mono/BI/OT:script=DFLT;language=dflt;"
No file main.aux.
\openout1 = `main.aux'.
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for TU/lmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
*geometry* driver: auto-detecting
*geometry* detected driver: xetex
*geometry* verbose mode - [ preamble ] result:
* driver: xetex
* paper: a4paper
* layout: <same size as paper>
* layoutoffset:(h,v)=(0.0pt,0.0pt)
* modes:
* h-part:(L,W,R)=(72.26999pt, 452.9679pt, 72.26999pt)
* v-part:(T,H,B)=(72.26999pt, 700.50687pt, 72.26999pt)
* \paperwidth=597.50787pt
* \paperheight=845.04684pt
* \textwidth=452.9679pt
* \textheight=700.50687pt
* \oddsidemargin=0.0pt
* \evensidemargin=0.0pt
* \topmargin=-37.0pt
* \headheight=12.0pt
* \headsep=25.0pt
* \topskip=10.0pt
* \footskip=30.0pt
* \marginparwidth=57.0pt
* \marginparsep=11.0pt
* \columnsep=10.0pt
* \skip\footins=9.0pt plus 4.0pt minus 2.0pt
* \hoffset=0.0pt
* \voffset=0.0pt
* \mag=1000
* \@twocolumnfalse
* \@twosidefalse
* \@mparswitchfalse
* \@reversemarginfalse
* (1in=72.27pt=25.4mm, 1cm=28.453pt)
Package fontspec Info:
(fontspec) Adjusting the maths setup (use [no-math] to avoid
(fontspec) this).
\symlegacymaths=\mathgroup4
LaTeX Font Info: Overwriting symbol font `legacymaths' in version `bold'
(Font) OT1/cmr/m/n --> OT1/cmr/bx/n on input line 11.
LaTeX Font Info: Redeclaring math accent \acute on input line 11.
LaTeX Font Info: Redeclaring math accent \grave on input line 11.
LaTeX Font Info: Redeclaring math accent \ddot on input line 11.
LaTeX Font Info: Redeclaring math accent \tilde on input line 11.
LaTeX Font Info: Redeclaring math accent \bar on input line 11.
LaTeX Font Info: Redeclaring math accent \breve on input line 11.
LaTeX Font Info: Redeclaring math accent \check on input line 11.
LaTeX Font Info: Redeclaring math accent \hat on input line 11.
LaTeX Font Info: Redeclaring math accent \dot on input line 11.
LaTeX Font Info: Redeclaring math accent \mathring on input line 11.
LaTeX Font Info: Redeclaring math symbol \colon on input line 11.
LaTeX Font Info: Redeclaring math symbol \Gamma on input line 11.
LaTeX Font Info: Redeclaring math symbol \Delta on input line 11.
LaTeX Font Info: Redeclaring math symbol \Theta on input line 11.
LaTeX Font Info: Redeclaring math symbol \Lambda on input line 11.
LaTeX Font Info: Redeclaring math symbol \Xi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Pi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Sigma on input line 11.
LaTeX Font Info: Redeclaring math symbol \Upsilon on input line 11.
LaTeX Font Info: Redeclaring math symbol \Phi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Psi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Omega on input line 11.
LaTeX Font Info: Redeclaring math symbol \mathdollar on input line 11.
LaTeX Font Info: Redeclaring symbol font `operators' on input line 11.
LaTeX Font Info: Encoding `OT1' has changed to `TU' for symbol font
(Font) `operators' in the math version `normal' on input line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `normal'
(Font) OT1/cmr/m/n --> TU/lmr/m/n on input line 11.
LaTeX Font Info: Encoding `OT1' has changed to `TU' for symbol font
(Font) `operators' in the math version `bold' on input line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `bold'
(Font) OT1/cmr/bx/n --> TU/lmr/m/n on input line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `normal'
(Font) TU/lmr/m/n --> TU/lmr/m/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathit' in version `normal'
(Font) OT1/cmr/m/it --> TU/lmr/m/it on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathbf' in version `normal'
(Font) OT1/cmr/bx/n --> TU/lmr/b/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathsf' in version `normal'
(Font) OT1/cmss/m/n --> TU/lmss/m/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathtt' in version `normal'
(Font) OT1/cmtt/m/n --> TU/HurmitNerdFontMono(0)/m/n on input
line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `bold'
(Font) TU/lmr/m/n --> TU/lmr/b/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathit' in version `bold'
(Font) OT1/cmr/bx/it --> TU/lmr/b/it on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathsf' in version `bold'
(Font) OT1/cmss/bx/n --> TU/lmss/b/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathtt' in version `bold'
(Font) OT1/cmtt/m/n --> TU/HurmitNerdFontMono(0)/b/n on input
line 11.
LaTeX Font Info: External font `cmex10' loaded for size
(Font) <7> on input line 14.
LaTeX Font Info: External font `cmex10' loaded for size
(Font) <5> on input line 14.
LaTeX Font Info: Font shape `TU/HurmitNerdFontMono(0)/m/n' will be
(Font) scaled to size 6.99997pt on input line 22.
LaTeX Warning: Float too large for page by 44.293pt on input line 93.
[1
] [2] (./main.aux)
***********
LaTeX2e <2025-11-01>
L3 programming layer <2026-01-19>
***********
)
Here is how much of TeX's memory you used:
3526 strings out of 470191
106539 string characters out of 5479698
562689 words of memory out of 5000000
32135 multiletter control sequences out of 15000+600000
627857 words of font info for 57 fonts, out of 8000000 for 9000
14 hyphenation exceptions out of 8191
73i,9n,93p,432b,328s stack positions out of 10000i,1000n,20000p,200000b,200000s
Output written on main.pdf (2 pages).

Binary file not shown.

View File

@@ -59,6 +59,7 @@
\languageline{expression}{\texttt{FVNCTIO} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\
\languageline{expression}{\textit{literal}} \\
\languageline{expression}{\textit{expression} \texttt{[} \textit{expression} \texttt{]}} \\
\languageline{expression}{\textit{expression} \texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{]} \textnormal{\small\ (inclusive slice)}} \\
\languageline{expression}{\textit{expression} \textbf{binop} \textit{expression}} \\
\languageline{expression}{\textbf{unop} \textit{expression}} \\ \hline
\languageline{literal}{\textbf{string}} \\

View File

@@ -0,0 +1,2 @@
DESIGNA x VT [X, XX, XXX, XL, L]
DICE(x[II VSQVE IV])

BIN
snippets/array_slice.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -11,7 +11,7 @@ from parameterized import parameterized
from fractions import Fraction
from centvrion.ast_nodes import (
ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray,
ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray,
Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement,
Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca,
ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement,
@@ -673,6 +673,15 @@ error_tests = [
("DESIGNA a, b VT III", CentvrionError), # destructure non-array
("DESIGNA a, b VT [I]", CentvrionError), # destructure length mismatch: too many targets
("DESIGNA a, b VT [I, II, III]", CentvrionError), # destructure length mismatch: too few targets
("[I, II, III][II VSQVE IV]", CentvrionError), # slice upper bound out of range
("[I, II, III][NVLLVS VSQVE II]", CentvrionError), # slice with non-integer bound
("I[I VSQVE II]", CentvrionError), # slice on non-array
("[I, II, III][III VSQVE I]", CentvrionError), # slice from > to
("CVM SVBNVLLA\n[I, II, III][-I VSQVE II]", CentvrionError), # slice with negative lower bound
("CVM SVBNVLLA\n[I, II, III][I VSQVE -I]", CentvrionError), # slice with negative upper bound
("CVM FRACTIO\n[I, II, III][IIIS VSQVE III]", CentvrionError), # slice with fractional lower bound
("CVM FRACTIO\n[I, II, III][I VSQVE IIIS]", CentvrionError), # slice with fractional upper bound
("CVM FRACTIO\n[I, II, III][I / II VSQVE III]", CentvrionError), # slice with division-fraction lower bound
]
class TestErrors(unittest.TestCase):
@@ -1517,6 +1526,63 @@ class TestArrayIndexAssign(unittest.TestCase):
run_test(self, source, nodes, value)
# --- Array slicing ---
array_slice_tests = [
# basic slice from middle
("[X, XX, XXX, XL, L][II VSQVE IV]",
Program([], [ExpressionStatement(ArraySlice(
DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX"), Numeral("XL"), Numeral("L")]),
Numeral("II"), Numeral("IV")))]),
ValList([ValInt(20), ValInt(30), ValInt(40)])),
# slice of length 1
("[I, II, III][II VSQVE II]",
Program([], [ExpressionStatement(ArraySlice(
DataArray([Numeral("I"), Numeral("II"), Numeral("III")]),
Numeral("II"), Numeral("II")))]),
ValList([ValInt(2)])),
# full array slice
("[I, II, III][I VSQVE III]",
Program([], [ExpressionStatement(ArraySlice(
DataArray([Numeral("I"), Numeral("II"), Numeral("III")]),
Numeral("I"), Numeral("III")))]),
ValList([ValInt(1), ValInt(2), ValInt(3)])),
# slice on variable
("DESIGNA a VT [I, II, III, IV, V]\na[II VSQVE IV]",
Program([], [
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V")])),
ExpressionStatement(ArraySlice(ID("a"), Numeral("II"), Numeral("IV"))),
]),
ValList([ValInt(2), ValInt(3), ValInt(4)])),
# slice then index (chained)
("[I, II, III, IV][I VSQVE III][II]",
Program([], [ExpressionStatement(ArrayIndex(
ArraySlice(
DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV")]),
Numeral("I"), Numeral("III")),
Numeral("II")))]),
ValInt(2)),
# slice on range array
("[I VSQVE X][III VSQVE VII]",
Program([], [ExpressionStatement(ArraySlice(
DataRangeArray(Numeral("I"), Numeral("X")),
Numeral("III"), Numeral("VII")))]),
ValList([ValInt(3), ValInt(4), ValInt(5), ValInt(6), ValInt(7)])),
# expression as slice bounds
("[I, II, III, IV, V][I + I VSQVE II + II]",
Program([], [ExpressionStatement(ArraySlice(
DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V")]),
BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS"),
BinOp(Numeral("II"), Numeral("II"), "SYMBOL_PLUS")))]),
ValList([ValInt(2), ValInt(3), ValInt(4)])),
]
class TestArraySlice(unittest.TestCase):
@parameterized.expand(array_slice_tests)
def test_array_slice(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Comments ---
comment_tests = [