Skip to content

Commit f7b5aa6

Browse files
authored
Merge pull request #2315 from GaijinEntertainment/string-lint
Add performance lint module, first_character/with_das_string builtins, optimize character_at
2 parents ae2adf9 + 1c00a50 commit f7b5aa6

26 files changed

+1213
-11
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: build
22

33
on:
44
push:
5+
branches: [master]
56
pull_request:
67
workflow_dispatch:
78
release:

CLAUDE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ Task-specific instructions are split into skill files under `skills/`. You MUST
9999
| `skills/aot_testing.md` | Adding AOT test files, working with the `test_aot` binary, `Module::aotRequire()`, CMake AOT macros, **debugging AOT hash mismatches** |
100100
| `skills/visitor_gen_bind.md` | Adding or modifying `Visitor` virtual methods, `canVisit*` gates, running `gen_bind.das`, updating adapter bindings in `ast_gen.inc` |
101101
| `skills/daslang_live.md` | Working with `daslang-live.exe`, live-reload lifecycle, REST API, `[live_command]`, `[before_reload]`/`[after_reload]`, persistent store, `live/glfw_live`, `live/live_api` |
102+
| `skills/perf_lint.md` | Adding new performance lint rules to `daslib/perf_lint.das` |
102103

103104
Multiple skill files may apply to a single task. For example, creating a new daslib module requires reading `skills/das_formatting.md`, `skills/daslib_modules.md`, and possibly `skills/documentation_rst.md`.
104105

@@ -174,6 +175,7 @@ All code MUST use gen2 syntax (add `options gen2` at the top of every file). Key
174175

175176
- `try/recover` — NOT `try/catch` (`recover` is the keyword)
176177
- `panic("message")`, `assert(condition)`, `verify(condition)` (stays in release)
178+
- **Postfix conditional:** `return expr if (cond)`, `break if (cond)`, `continue if (cond)` — early-exit guard on one line
177179

178180
### Generic function dispatch
179181

@@ -196,6 +198,13 @@ All code MUST use gen2 syntax (add `options gen2` at the top of every file). Key
196198
- When the iterator is named `each`, the call can be omitted: `for (v in each(x))` is identical to `for (v in x)`
197199
- Other iterator names (e.g. `filter`, `map`) cannot be omitted
198200

201+
### String access functions
202+
203+
- **`peek_data(str) $(arr) { ... }`** — safe O(1) per-element read access to string as `array<uint8> const#`. One `strlen` call total. Preferred over `character_at` for iteration.
204+
- **`modify_data(str) $(var arr) { ... }`** — returns a modified copy; allocates new string, opens as mutable `array<uint8>`. Use for character-level transformations.
205+
- **`character_at(s, i)`** — O(n) per call (`strlen` + bounds check). Fine for isolated checks, but use `peek_data` in loops or hot paths.
206+
- Pointer-based string access (`reinterpret<uint8?>`) is for core library implementations only — user code should use `peek_data`/`modify_data` for safety.
207+
199208
### Common gotchas
200209

201210
- Lambda params can shadow function params — use distinct names

daslib/aot_cpp.das

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,7 +1655,7 @@ class public CppAot : AstVisitor {
16551655
}
16561656
}
16571657
def isOpPolicy1(that : smart_ptr<ExprOp1>) {
1658-
if (is_alpha(character_at(string(that.op), 0))) {
1658+
if (is_alpha(first_character(that.op))) {
16591659
return true;
16601660
}
16611661
return that.subexpr._type.isPolicyType;
@@ -1688,8 +1688,7 @@ class public CppAot : AstVisitor {
16881688
write(*ss, ",*__context__,nullptr)");
16891689
} else {
16901690
if (that.op == "+++" || that.op == "---") {
1691-
*ss |> write_char(character_at(string(that.op), 0))
1692-
*ss |> write_char(character_at(string(that.op), 1));
1691+
write(*ss, slice(string(that.op), 0, 2));
16931692
}
16941693
if (!noBracket(that) && !that.subexpr.printFlags.bottomLevel) {
16951694
write(*ss, ")");
@@ -1703,7 +1702,7 @@ class public CppAot : AstVisitor {
17031702
that.right._type.baseType == Type.tBool && that.right._type.isSimpleType);
17041703
}
17051704
def isOpPolicy2(that : smart_ptr<ExprOp2>) {
1706-
if (is_alpha(character_at(string(that.op), 0))) return true;
1705+
if (is_alpha(first_character(that.op))) return true;
17071706
if (that.op == "/" || that.op == "%") return true;
17081707
if (that.op == "<<<" || that.op == ">>>" || that.op == "<<<=" || that.op == ">>>=") return true;
17091708
if (that.op == "<<" || that.op == ">>" || that.op == "<<=" || that.op == ">>=") return true;
@@ -2537,10 +2536,10 @@ class public CppAot : AstVisitor {
25372536
write(*ss, "das_swizzle_ref<{type_str},{value_str},{int(expr.fields[0])}>::swizzle(");
25382537
} else {
25392538
if (length(expr.fields) == 1) {
2540-
let mask = "xyzw";
2539+
let mask = fixed_array('x', 'y', 'z', 'w');
25412540
let is64bit = expr._type.baseType == Type.tInt64 || expr._type.baseType == Type.tUInt64;
25422541
write(*ss, "v_extract_")
2543-
*ss |> write_char(character_at(mask, int(expr.fields[0])));
2542+
*ss |> write_char(mask[int(expr.fields[0])]);
25442543
if (expr._type.baseType != Type.tFloat) {
25452544
write(*ss, is64bit ? "i64" : "i");
25462545
}

0 commit comments

Comments
 (0)