Skip to content

Commit 10be517

Browse files
Enhance TechLang with STL compatibility, dynamic arrays, and improved control flow operators; update documentation and examples for new features and error handling improvements.
1 parent 0c5782f commit 10be517

15 files changed

Lines changed: 399 additions & 117 deletions

AGENTS.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,8 +1170,35 @@ tl> # State persists!
11701170

11711171
---
11721172

1173-
**Last Updated:** 2025-11-09
1174-
**Total Features Added:** 8
1175-
**Total Tests:** 333+ (233 core + 40 stl + 6 macros + rest)
1173+
### 2025-12-14: STL Compatibility + Deterministic Process Status
1174+
1175+
**Status:** ✅ Completed
1176+
1177+
### Summary
1178+
Improved core runtime semantics to support the existing `stl/*` modules and stabilized `proc_status` behavior on Windows.
1179+
1180+
### Implementation Details
1181+
- Added “store into target” forms for core string/array operations (`str_length`, `str_substring`, `str_contains`, `array_get`) so STL helpers can compute without emitting intermediate output.
1182+
- Added dynamic arrays (`array_create <name>` with no size) that grow via `array_set` and allow sentinel `0` on out-of-bounds `array_get ... <target>`.
1183+
- Expanded control-flow operator support to include `eq/ne/gt/lt/ge/le` aliases and allowed string equality in `if` conditions.
1184+
- Made `proc_spawn "python" ...` use the active interpreter and made `proc_status` less flaky by doing a short internal wait when appropriate.
1185+
1186+
### Files Modified
1187+
- techlang/core.py
1188+
- techlang/data_types.py
1189+
- techlang/control_flow.py
1190+
- techlang/variables.py
1191+
- techlang/imports.py
1192+
- techlang/system_ops.py
1193+
- stl/strings.tl
1194+
1195+
### Validation
1196+
-`D:/TechLang/.venv/Scripts/python.exe -m pytest -q``324 passed, 7 skipped`
1197+
1198+
---
1199+
1200+
**Last Updated:** 2025-12-14
1201+
**Total Features Added:** 9
1202+
**Total Tests:** 331 collected (324 passing, 7 skipped)
11761203
**REPL Version:** 1.1 - Enhanced Edition
11771204

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ TechLang/
4848
│ ├── linter.py # Code linter
4949
│ └── __init__.py
5050
51-
├── tests/ # Pytest-based unit tests (255 tests)
51+
├── tests/ # Pytest-based unit tests (331 collected; 324 passing, 7 skipped)
5252
│ ├── test_interpreter.py
5353
│ ├── test_database.py
5454
│ ├── test_database_advanced.py

docs/control-flow.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ if score < 60
1717
end
1818
```
1919

20-
Comparisons support `==`, `!=`, `<`, `<=`, `>` and `>=`. The right-hand side accepts integers or existing variables.
20+
Comparisons support `==`, `!=`, `<`, `<=`, `>` and `>=`.
21+
22+
For convenience (and for compatibility with some examples), these operator aliases are also accepted:
23+
24+
- `eq/ne/gt/lt/ge/le` map to `==/!=/>/</>=/<=`.
25+
26+
The right-hand side accepts integers or existing variables. Equality/inequality also works with string operands (for example comparing two string variables).
2127

2228
## Loops
2329

docs/data-types.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ str_create message "hello world"
6767
str_length message # Outputs: 11
6868
str_concat message " again" # Appends to message
6969
print message # hello world again
70+
71+
# Store length into a variable (no output)
72+
str_length message len
73+
print len # 17
7074
```
7175

7276
### String Transformation
@@ -109,8 +113,16 @@ str_create text "techlang is awesome"
109113
str_contains text "lang" # 1 (true)
110114
str_contains text "python" # 0 (false)
111115
116+
// Store contains result into a variable (no output)
117+
str_contains text "lang" found
118+
print found # 1
119+
112120
// Get substring
113121
str_substring text 0 8 # "techlang"
122+
123+
// Store substring into a string variable (no output)
124+
str_substring text 0 8 head
125+
print head # techlang
114126
```
115127

116128
### String Operations Summary
@@ -141,6 +153,24 @@ array_push numbers 20
141153
array_pop numbers
142154
```
143155

156+
### Dynamic arrays (size optional)
157+
158+
You can also create a dynamic array without a fixed size:
159+
160+
```techlang
161+
array_create nums
162+
array_push nums 5
163+
array_push nums 10
164+
165+
# Store into a variable instead of printing
166+
array_get nums 1 value
167+
print value # 10
168+
```
169+
170+
For dynamic arrays, `array_set` grows the array automatically.
171+
172+
For dynamic arrays, out-of-bounds `array_get <array> <index> <target>` stores `0` (sentinel) into `<target>`. (Without a target, out-of-bounds reads still raise an error.)
173+
144174
### Array Operations
145175

146176
| Command | Description |

docs/system.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ The process commands let you start a long-running program and interact with it l
3131

3232
Commands that accept a path or command string must use double quotes. All commands run relative to the interpreter's base directory, so you can reference project scripts directly.
3333

34+
Notes:
35+
36+
- On Windows, `proc_spawn "python" ...` uses the interpreter running TechLang (avoids the Microsoft Store alias) for more predictable behavior.
37+
- `proc_status` may do a short internal wait once a process has been alive briefly, to reduce flakiness from slow process startup.
38+
3439
```techlang
3540
proc_spawn "\"python\" -c \"import time; time.sleep(1)\""
3641
proc_status 1 # running

stl/README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,28 @@
33
The TechLang STL provides reusable modules for common programming tasks. All modules follow the export/public API system for clean interfaces.
44

55
**Alias:** You can use `stl` as an alias for `stl` (for compatibility):
6-
- `package use stl/validation` (or `stl/validation`)
7-
- `call stl.validation.is_positive` (or `stl.validation.is_positive`)
6+
**Alias:** You can use `stdlib` as an alias for `stl` (for compatibility):
7+
- `package use stdlib/validation` (or `stl/validation`)
8+
- `call stdlib.validation.is_positive` (or `stl.validation.is_positive`)
9+
10+
## ✅ Runtime Assumptions (STL Compatibility)
11+
12+
Some STL modules rely on “store into target” forms of core commands (so helpers can avoid printing intermediate values):
13+
14+
- `str_length <string> <targetVar>` stores the length in `<targetVar>`.
15+
- `str_substring <string> <start> <end> <targetString>` stores into `<targetString>`.
16+
- `str_contains <string> <substring> <targetVar>` stores `1/0` into `<targetVar>`.
17+
- `array_get <array> <index> <target>` stores the value into `<target>`.
18+
19+
Arrays used by STL are typically **dynamic arrays** created via:
20+
21+
- `array_create <name>` (no size) creates a dynamic array.
22+
- For dynamic arrays, `array_set` grows as needed.
23+
- For dynamic arrays, out-of-bounds `array_get ... <target>` stores `0` (sentinel) instead of erroring. This is used by `stl/collections` loops.
24+
25+
Control-flow also accepts operator synonyms used in examples:
26+
27+
- `eq/ne/gt/lt/ge/le` map to `==/!=/>/</>=/<=`.
828

929
## 📚 Available Modules
1030

stl/strings.tl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,29 @@ def is_empty s
3333
end
3434

3535
def capitalize source_text
36-
# Capitalize first character (simple version - just uppercase first char, keep rest as-is)
36+
# Capitalize first character and lowercase the rest
3737
# Args: source_text - string to capitalize
3838
# Returns: capitalized string
39-
# Note: This is a simplified implementation due to scoping limitations
39+
str_length source_text len
40+
if len == 0
41+
str_create result ""
42+
return result
43+
end
44+
45+
str_substring source_text 0 1 first
46+
str_substring source_text 1 len rest
47+
48+
str_create first_up ""
49+
str_concat first_up first
50+
str_upper first_up
51+
52+
str_create rest_low ""
53+
str_concat rest_low rest
54+
str_lower rest_low
55+
4056
str_create result ""
41-
str_concat result source_text
42-
str_upper result
57+
str_concat result first_up
58+
str_concat result rest_low
4359
return result
4460
end
4561

techlang.db

8 KB
Binary file not shown.

techlang/control_flow.py

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def handle_while(state: InterpreterState, tokens: List[str], index: int, execute
8383
# Get current variable value for comparison
8484
var_value = state.get_variable(varname, 0)
8585
if not isinstance(var_value, int):
86-
state.add_error(f"Variable '{varname}' is not a number. Cannot perform comparison.")
86+
state.add_error(f"Expected a number for variable '{varname}', but got '{var_value}'.")
8787
return 0
8888

8989
# Check if condition is still true
@@ -114,30 +114,34 @@ def handle_if(state: InterpreterState, tokens: List[str], index: int, execute_bl
114114
varname = tokens[index + 1]
115115
op = tokens[index + 2]
116116
compare_token = tokens[index + 3]
117-
118-
# Try to resolve compare_token as a variable first, then as a literal
119-
compare_val = state.get_variable(compare_token, None)
120-
if compare_val is None:
117+
118+
start_index = index + 4 # Start after 'if', variable, operator, and value
119+
120+
# Resolve left operand: variables first, then strings (for stl-style string comparisons)
121+
if state.has_variable(varname):
122+
var_value: Union[int, str] = state.get_variable(varname, 0)
123+
elif varname in state.strings:
124+
var_value = state.strings[varname]
125+
else:
126+
var_value = 0
127+
128+
# Resolve right operand: variables, strings, quoted literals, then ints
129+
if state.has_variable(compare_token):
130+
compare_val: Union[int, str] = state.get_variable(compare_token, 0)
131+
elif compare_token in state.strings:
132+
compare_val = state.strings[compare_token]
133+
elif compare_token.startswith('"') and compare_token.endswith('"'):
134+
compare_val = compare_token[1:-1]
135+
else:
121136
try:
122137
compare_val = int(compare_token)
123138
except ValueError:
124-
state.add_error(f"Expected a number or variable for comparison, but got '{compare_token}'. Please provide a valid integer or variable name.")
139+
state.add_error(
140+
f"Expected a number or variable for comparison, but got '{compare_token}'. Please provide a valid integer or variable name."
141+
)
125142
return 0
126-
127-
start_index = index + 4 # Start after 'if', variable, operator, and value
128-
129-
# Get variable value for comparison
130-
var_value = state.get_variable(varname, 0)
131-
if not isinstance(var_value, int):
132-
state.add_error(f"Variable '{varname}' is not a number. Cannot perform comparison.")
133-
return 0
134-
135-
# Ensure compare_val is an integer
136-
if not isinstance(compare_val, int):
137-
state.add_error(f"Comparison value must be a number, but got type '{type(compare_val).__name__}'.")
138-
return 0
139-
140-
# Check condition
143+
144+
# Check condition (supports string equality and operator synonyms)
141145
condition_met = ControlFlowHandler._evaluate_condition(var_value, op, compare_val)
142146

143147
# Collect the if block
@@ -686,18 +690,35 @@ def _extract_error_message(line: str) -> str:
686690
return line
687691

688692
@staticmethod
689-
def _evaluate_condition(var_value: int, op: str, compare_val: int) -> bool:
690-
if op == "==":
693+
def _evaluate_condition(var_value: Union[int, str], op: str, compare_val: Union[int, str]) -> bool:
694+
# Support common word operators used in examples/stdlib
695+
op_map = {
696+
"eq": "==",
697+
"ne": "!=",
698+
"gt": ">",
699+
"lt": "<",
700+
"ge": ">=",
701+
"le": "<=",
702+
}
703+
normalized_op = op_map.get(op, op)
704+
705+
# Equality works across types (mismatched types simply compare False/True)
706+
if normalized_op == "==":
691707
return var_value == compare_val
692-
elif op == "!=":
708+
if normalized_op == "!=":
693709
return var_value != compare_val
694-
elif op == ">":
710+
711+
# Ordering comparisons require integers
712+
if not isinstance(var_value, int) or not isinstance(compare_val, int):
713+
return False
714+
715+
if normalized_op == ">":
695716
return var_value > compare_val
696-
elif op == "<":
717+
if normalized_op == "<":
697718
return var_value < compare_val
698-
elif op == ">=":
719+
if normalized_op == ">=":
699720
return var_value >= compare_val
700-
elif op == "<=":
721+
if normalized_op == "<=":
701722
return var_value <= compare_val
702-
else:
703-
return False # Unknown operator
723+
724+
return False # Unknown operator

techlang/core.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class InterpreterState:
5050

5151
# Arrays that can store lists of numbers or text
5252
arrays: Dict[str, List[Union[int, str]]] = None
53+
54+
# Arrays created without a fixed size (allow growth via array_set and safe out-of-bounds reads)
55+
dynamic_arrays: Set[str] = None
5356

5457
# Text strings that can be manipulated
5558
strings: Dict[str, str] = None
@@ -75,6 +78,9 @@ class InterpreterState:
7578
# Subprocess management
7679
processes: Dict[int, object] = None
7780
next_process_id: int = 1
81+
82+
# Subprocess timing (used for more deterministic status polling)
83+
process_start_times: Dict[int, float] = None
7884

7985
# Debugger state
8086
breakpoints: Set[int] = None # Line numbers where execution should pause
@@ -119,6 +125,8 @@ def __post_init__(self):
119125
self.loaded_modules = set()
120126
if self.arrays is None:
121127
self.arrays = {}
128+
if self.dynamic_arrays is None:
129+
self.dynamic_arrays = set()
122130
if self.strings is None:
123131
self.strings = {}
124132
if self.dictionaries is None:
@@ -139,6 +147,8 @@ def __post_init__(self):
139147
self.queues = {}
140148
if self.processes is None:
141149
self.processes = {}
150+
if self.process_start_times is None:
151+
self.process_start_times = {}
142152
if self.breakpoints is None:
143153
self.breakpoints = set()
144154
if self.watched_vars is None:
@@ -161,6 +171,7 @@ def reset(self) -> None:
161171
self.aliases.clear()
162172
self.input_queue.clear()
163173
self.arrays.clear()
174+
self.dynamic_arrays.clear()
164175
self.strings.clear()
165176
self.dictionaries.clear()
166177
self.struct_defs.clear()
@@ -174,6 +185,7 @@ def reset(self) -> None:
174185
self.queues.clear()
175186
self.processes.clear()
176187
self.next_process_id = 1
188+
self.process_start_times.clear()
177189
self.modules.clear()
178190
self.loaded_modules.clear()
179191

0 commit comments

Comments
 (0)