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
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: build

on:
push:
branches: [master]
pull_request:
workflow_dispatch:
release:
Expand Down
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Task-specific instructions are split into skill files under `skills/`. You MUST
| `skills/aot_testing.md` | Adding AOT test files, working with the `test_aot` binary, `Module::aotRequire()`, CMake AOT macros, **debugging AOT hash mismatches** |
| `skills/visitor_gen_bind.md` | Adding or modifying `Visitor` virtual methods, `canVisit*` gates, running `gen_bind.das`, updating adapter bindings in `ast_gen.inc` |
| `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` |
| `skills/perf_lint.md` | Adding new performance lint rules to `daslib/perf_lint.das` |

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`.

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

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

### Generic function dispatch

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

### String access functions

- **`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.
- **`modify_data(str) $(var arr) { ... }`** — returns a modified copy; allocates new string, opens as mutable `array<uint8>`. Use for character-level transformations.
- **`character_at(s, i)`** — O(n) per call (`strlen` + bounds check). Fine for isolated checks, but use `peek_data` in loops or hot paths.
- Pointer-based string access (`reinterpret<uint8?>`) is for core library implementations only — user code should use `peek_data`/`modify_data` for safety.

### Common gotchas

- Lambda params can shadow function params — use distinct names
Expand Down
11 changes: 5 additions & 6 deletions daslib/aot_cpp.das
Original file line number Diff line number Diff line change
Expand Up @@ -1655,7 +1655,7 @@ class public CppAot : AstVisitor {
}
}
def isOpPolicy1(that : smart_ptr<ExprOp1>) {
if (is_alpha(character_at(string(that.op), 0))) {
if (is_alpha(first_character(that.op))) {
return true;
}
return that.subexpr._type.isPolicyType;
Expand Down Expand Up @@ -1688,8 +1688,7 @@ class public CppAot : AstVisitor {
write(*ss, ",*__context__,nullptr)");
} else {
if (that.op == "+++" || that.op == "---") {
*ss |> write_char(character_at(string(that.op), 0))
*ss |> write_char(character_at(string(that.op), 1));
write(*ss, slice(string(that.op), 0, 2));
}
if (!noBracket(that) && !that.subexpr.printFlags.bottomLevel) {
write(*ss, ")");
Expand All @@ -1703,7 +1702,7 @@ class public CppAot : AstVisitor {
that.right._type.baseType == Type.tBool && that.right._type.isSimpleType);
}
def isOpPolicy2(that : smart_ptr<ExprOp2>) {
if (is_alpha(character_at(string(that.op), 0))) return true;
if (is_alpha(first_character(that.op))) return true;
if (that.op == "/" || that.op == "%") return true;
if (that.op == "<<<" || that.op == ">>>" || that.op == "<<<=" || that.op == ">>>=") return true;
if (that.op == "<<" || that.op == ">>" || that.op == "<<=" || that.op == ">>=") return true;
Expand Down Expand Up @@ -2537,10 +2536,10 @@ class public CppAot : AstVisitor {
write(*ss, "das_swizzle_ref<{type_str},{value_str},{int(expr.fields[0])}>::swizzle(");
} else {
if (length(expr.fields) == 1) {
let mask = "xyzw";
let mask = fixed_array('x', 'y', 'z', 'w');
let is64bit = expr._type.baseType == Type.tInt64 || expr._type.baseType == Type.tUInt64;
write(*ss, "v_extract_")
*ss |> write_char(character_at(mask, int(expr.fields[0])));
*ss |> write_char(mask[int(expr.fields[0])]);
if (expr._type.baseType != Type.tFloat) {
write(*ss, is64bit ? "i64" : "i");
}
Expand Down
10 changes: 6 additions & 4 deletions daslib/constant_expression.das
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,12 @@ class ConstantExpressionMacro : AstFunctionAnnotation {
return <- default<ExpressionPtr>
}
var inscope func_copy <- clone_function(expr.func)
var func_name = "{func_copy.name}`constant_expression"
for (i in argi) {
let fhash = hash(describe(expr.arguments[i]))
func_name += "`{fhash}"
var func_name = build_string() <| $(var w) {
w |> write("{func_copy.name}`constant_expression")
for (i in argi) {
let fhash = hash(describe(expr.arguments[i]))
w |> write("`{fhash}")
}
}
func_copy.name := func_name
if (expr.func.fromGeneric != null) {
Expand Down
10 changes: 5 additions & 5 deletions daslib/coverage.das
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require rtti
require strings

require daslib/ast_boost
require daslib/strings_boost
require daslib/templates_boost


Expand Down Expand Up @@ -120,12 +121,11 @@ def private single_file_report(name : string) {
def get_report(name : string = "") : string {
//! Returns code coverage report in lcov format for the specified file, or all files if name is empty.
if (name |> empty) {
var res = ""

for (k in keys(coverageData)) {
res += single_file_report(k)
return build_string() <| $(var w) {
for (k in keys(coverageData)) {
w |> write(single_file_report(k))
}
}
return res
} else {
return single_file_report(name)
}
Expand Down
50 changes: 26 additions & 24 deletions daslib/interfaces.das
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module interfaces shared private

require daslib/ast_boost
require daslib/templates_boost
require daslib/strings_boost
require daslib/defer
require daslib/generic_return
require strings
Expand Down Expand Up @@ -275,31 +276,32 @@ class ImplementsMacro : AstStructureAnnotation {
return true // apply already reported this error
}
// Check each interface method
var missing : string
for (ifld in iface.fields) {
let ifld_name = string(ifld.name)
// Skip compiler-generated fields
if (ifld_name == "__rtti" || ifld_name == "__finalize") {
continue
}
if (!ifld._type.isFunction) {
continue
}
// Methods with a default implementation (init != null) are optional
if (ifld.init != null) {
continue
}
// Abstract method — struct must provide {iface_name}`{method_name}
let expected = "{iface_name}`{ifld_name}"
var found = false
for (sfld in st.fields) {
if (string(sfld.name) == expected) {
found = true
break
var missing = build_string() <| $(var w) {
for (ifld in iface.fields) {
let ifld_name = string(ifld.name)
// Skip compiler-generated fields
if (ifld_name == "__rtti" || ifld_name == "__finalize") {
continue
}
if (!ifld._type.isFunction) {
continue
}
// Methods with a default implementation (init != null) are optional
if (ifld.init != null) {
continue
}
// Abstract method — struct must provide {iface_name}`{method_name}
let expected = "{iface_name}`{ifld_name}"
var found = false
for (sfld in st.fields) {
if (string(sfld.name) == expected) {
found = true
break
}
}
if (!found) {
w |> write("{st.name} does not implement {iface_name}.{ifld_name}\n")
}
}
if (!found) {
missing = "{missing}{st.name} does not implement {iface_name}.{ifld_name}\n"
}
}
if (length(missing) > 0) {
Expand Down
2 changes: 1 addition & 1 deletion daslib/json_boost.das
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ def public parse_json_annotation(name : string; annotation : array<tuple<name :
if (ann.name == "rename") {
if (ann.data is tString) {
fieldState.argName = ann.data as tString
} elif (ann.data is tBool && length(name) > 0 && character_at(name, 0) == '_') {
} elif (ann.data is tBool && length(name) > 0 && first_character(name) == '_') {
fieldState.argName = slice(name, 1)
}
} elif (ann.name == "enum_as_int" && ann.data is tBool) {
Expand Down
Loading
Loading