Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/c-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: C/C++ CI

on:
push:
branches: [ dev, stable, dev-0.9.9, dev-1.0.0]
branches: [ dev, stable, dev-0.9.9, dev-1.0.0, dev-rolling-context]
pull_request:
branches: [ dev, stable, dev-0.9.9, dev-1.0.0]
branches: [ dev, stable, dev-0.9.9, dev-1.0.0, dev-rolling-context]

env:
SPECS_BRANCH: ${{ github.event.pull_request.base.ref || github.ref_name }}
Expand Down
58 changes: 58 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Agent Guidelines for specs2016

## General Project Guidelines

- When adding or removing tests in `specs/src/test/ProcessingTest.cc`, also update `count_processing_tests` in `specs/tests/valgrind_unit_tests.py`.
- When adding or removing tests in `specs/src/test/ALUUnitTest.cc`, also update `count_ALU_tests` in `specs/tests/valgrind_unit_tests.py`.
- When adding or removing tests in `specs/src/test/TokenTest.cc`, also update `count_token_tests` in `specs/tests/valgrind_unit_tests.py`.
- Keep any new `ProcessingTest.cc` regression cases appended at the end when possible, to avoid unnecessary renumbering.
- `MYASSERT(cond)` and `MYASSERT_WITH_MSG(cond, msg)` (defined in `specs/src/utils/ErrorReporting.h`) are **always-on runtime checks** that throw `SpecsException` via `MYTHROW`. They are *not* compiled out by `NDEBUG`. Do not add redundant `if`/`MYTHROW` guards that duplicate what a `MYASSERT` already covers.
- When building the project, always use the command-line `make clean all`. Do not skip the `clean` target, and do not use the `-j` argument.
- When changing the structure of any of the classes that have dump_ macros in `specs_gdb.py` and `specs.gdb` update the relevant macros as well.

## Rolling Context Feature (Issue #106)

### Token Normalization in tokens.cc

The CONTEXT keyword requires special handling during token normalization (`normalizeTokenList` in `tokens.cc`):

1. **Initial State**: When a CONTEXT token is first parsed, its `Literal()` field is empty.

2. **Normalization Process** (lines 920-951 in tokens.cc):
- Check if the next token exists: `i+1 < tokList->size()`
- If yes, extract the integer offset from the next token (which can be a RANGE or a literal)
- Store the offset string in the CONTEXT token's literal field
- Erase the next token from the list
- If no next token exists, the literal remains empty

3. **Compile-Time Validation** (lines 473-489 in specItems.cc):
- When `itemGroup::Compile` processes a CONTEXT token, it must validate that the literal is not empty
- Use `tokenVec[index].argIndex()` (not the vector index) for error messages
- Call `std::stoi(tokenVec[index].Literal())` only after validation

### Key Insight

The `if` statement at line 923 in tokens.cc checks `tok.Literal()==""` because:
- If there is no next token (`i+1 >= tokList->size()`), the condition is false
- The literal is never set
- The error is caught at compile time in specItems.cc with a proper error message

**Example that triggers the error:**
```
specs '1-* 1 CONTEXT'
```
This produces: "CONTEXT at index X must be followed by an integer offset"

### Testing

All rolling context tests are in `specs/src/test/ProcessingTest.cc` (tests #229-#241).
Run with: `../exe/ProcessingTest`

### Verbose Output

When rolling context is used and verbose mode is enabled (`-v` flag), the buffer sizes are printed:
```
Rolling context buffer sizes: forward=<n> backward=<m>
```

This is implemented in `specs/src/test/specs.cc` after the compilation phase.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ What's new:
* Support Python in `MSBuild` builds
* Added MSI and stand-alone Windows executable to release artifacts
* Debugging aids for GDB
* Rolling context support

***
1-May-2026: Version 0.9.9 is here
Expand Down Expand Up @@ -78,8 +79,10 @@ When starting a new version:

Contributors
============
* Yoav Nir ([yoavnir](https://github.com/yoavnir))
* Jean-Baptiste Jouband ([Gawesomer](https://github.com/Gawesomer))
- Yoav Nir ([yoavnir](https://github.com/yoavnir))
- Jean-Baptiste Jouband ([Gawesomer](https://github.com/Gawesomer))
- donglrd ([donglrd](https://github.com/donglrd))
- Miriam-R-coder ([Miriam-R-coder](https://github.com/Miriam))

Documentation
=============
Expand Down
9 changes: 0 additions & 9 deletions agents.md

This file was deleted.

66 changes: 66 additions & 0 deletions manpage
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,48 @@ Similarly:
specs w1 a: SKIP-UNTIL "a=BEGIN" 1-* 1
.P
skips records until the first record whose first word is BEGIN, then processes that record and all subsequent records normally.
.IP "CONTEXT" 3
Temporarily changes the working string to a nearby record without consuming it.
.B CONTEXT
is followed by an integer offset:
.B CONTEXT 0
resets the working string to the current record,
.B CONTEXT 1
sets it to the next record,
.B CONTEXT -1
sets it to the previous record, and so on.
The required buffer sizes are computed at parse time from the largest positive and negative offsets used. If the offset refers to a record beyond the beginning or end of the input, the working string is set to the empty string.
.P
.B CONTEXT
must not be abbreviated. It is not supported with threading or multiple input streams.
.P
Example:
.P
echo -e "A\\nB\\nC" | specs 1-* 1 CONTEXT 1 1-* NW
.P
produces: "A B", "B C", "C" -- each line shows the current record followed by the next.
.P
In expressions, the syntax
.B @+n
and
.B @-n
provides access to nearby records without changing the working string. For example,
.B @+1
refers to the next record and
.B @-1
refers to the previous record.
.B @+0
and
.B @-0
are equivalent to
.B @@
(the current record).
.P
Example:
.P
echo -e "A\\nB\\nC" | specs print @+1 1
.P
produces: "B", "C", "" -- each line shows the next record (empty for the last).

.SS "MainOptions"
These are optional spec units that appear at the beginning of the specification and modify the behavior of the entire specification.
Expand Down Expand Up @@ -680,6 +722,21 @@ will output 4. The assignment is performed and the value stored in the counter.

.IP "At-sign (@) Operator" 3
The at-sign allows the inclusion of user-defined and system-defined labels as strings in expressions. The double at-sign (@@) substitutes for the entire input record.
The syntax
.B @+n
and
.B @-n
(where n is a natural number) accesses nearby records from the rolling context buffer.
.B @+1
is the next record,
.B @-1
is the previous record.
.B @+0
and
.B @-0
are equivalent to
.B @@.
Out-of-bounds offsets return the empty string. The forward and backward buffer sizes are computed automatically at parse time. This feature requires non-threaded mode and a single input stream.

.SS "Built-In Functions"
.IP "abs(x)" 3
Expand Down Expand Up @@ -855,6 +912,15 @@ or
spec units, then this number will be equal to the result of
.B number().
Otherwise, it will be higher.
.IP "ctxrecno()" 3
Returns the record number of the record that input parts work on. This is similar to
.B recno(),
but considers rolling context, which
.B recno()
does not. When no
.I CONTEXT
is active, returns the same value as
.B recno().
.IP "record()" 3
Returns the entire input record. Equivalent to
.B range(1,-1)
Expand Down
1 change: 1 addition & 0 deletions specs/docs/alu_adv.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ All three regular expression functions have an argument called `matchFlags`. Thi
| `number()` | Returns the number of processing cycles we have already gone through. Unless `READ` or `READSTOP` are used, this will be equal to the number of records read so far. |
| `range(n,m)` | Returns the substring from the *n*-th character (default first) to the *m*-th character (default last) |
| `recno()` | Returns the number of the currently read record. If the `READ` or `READSTOP` keywords are used this may be greater than `number()` |
| `ctxrecno()` | Returns the record number of the record that input parts work on. This is similar to `recno()`, but considers rolling context, which `recno()` does not. |
| `record()` | Returns the entire input record |
| `word(n)` | Returns the *n*-th word |
| `wordrange(n,m)` | Returns the substring from the *n*-th word (default first) to the *m*-th word (default last) |
Expand Down
Loading
Loading