diff --git a/.github/workflows/wasm_build.yml b/.github/workflows/wasm_build.yml index 70f50aa5c0..8c641516b1 100644 --- a/.github/workflows/wasm_build.yml +++ b/.github/workflows/wasm_build.yml @@ -2,6 +2,7 @@ name: wasm_build on: push: + branches: [master] pull_request: workflow_dispatch: @@ -122,3 +123,8 @@ jobs: cd cmake_temp cmake -DCMAKE_BUILD_TYPE:STRING=${{ matrix.cmake_preset }} -G Ninja -DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake ../ ninja + + - name: "Test: hello world via Node.js" + run: | + cd web + node test/dastest_wasm.js ../ ./output diff --git a/daslib/aot_cpp.das b/daslib/aot_cpp.das index 2bd8278ae3..4f58517dc5 100644 --- a/daslib/aot_cpp.das +++ b/daslib/aot_cpp.das @@ -2935,7 +2935,7 @@ class public CppAot : AstVisitor { tab ++; if (!expr.makeStructFlags.isNewHandle) { if (!needTempSrc(expr)) { - let expr_type = describeCppType(expr._type, DescribeConfig(skip_ref = true, cross_platform = cross_platform)); + let expr_type = describeCppType(expr._type, DescribeConfig(skip_ref = true, skip_const = true, cross_platform = cross_platform)); write(*ss, "{tabs()}{expr_type} {mksName(expr)}"); if (expr.constructor != null) { write(*ss, " = "); diff --git a/daslib/option.das b/daslib/option.das index fcd9ac1b2a..07118cdf6d 100644 --- a/daslib/option.das +++ b/daslib/option.das @@ -24,13 +24,15 @@ require daslib/typemacro_boost //! Tagged ``some``/``none`` pair over payload type ``T``. //! -//! When ``_has_value`` is ``false``, ``value`` holds the zero value of ``T`` and -//! must not be read. Accessor functions should be used for reads/writes. -[template_structure(T), safe_when_uninitialized] +//! Resolves to ``tuple<_has_value : bool; _value : T>`` — a structural type with +//! no module home, so ``Option`` reached through any chain of ``require ... +//! public`` collapses to one canonical ``tTuple`` candidate (issue #2598). +//! When ``_has_value`` is ``false``, ``_value`` holds the zero value of ``T`` +//! and must not be read. Use the accessor functions below for reads / writes. +[template_tuple(T)] struct template Option { _has_value : bool = false - @safe_when_uninitialized - _value : T + @safe_when_uninitialized _value : T } //! Construct ``Option`` carrying ``v``. Payload type is inferred from ``v``. @@ -55,8 +57,7 @@ def some(v : auto(TT)) { //! clone; for workhorse types (``int``, ``float``, ``string``, …) ``some`` is //! equivalent and cheaper to read. def move_some(var v : auto(TT)) { - typedef OT = $Option < TT - const > - return <- OT(_has_value = true, _value <- v) + return <- (_has_value = true, _value <- v) } //! Construct an empty ``Option``. Pass the payload type as a witness:: diff --git a/daslib/result.das b/daslib/result.das index b16b3631dd..794959d7ea 100644 --- a/daslib/result.das +++ b/daslib/result.das @@ -26,17 +26,18 @@ require daslib/option public //! Tagged ``ok``/``err`` pair over payload ``T`` and error ``E``. //! -//! When ``is_ok`` is ``true``, ``value`` is meaningful and ``error`` holds the zero -//! value of ``E``. When ``is_ok`` is ``false``, ``error`` is meaningful and ``value`` -//! holds the zero value of ``T``. Prefer the accessor functions below over direct -//! field reads. -[template_structure(T, E)] +//! Resolves to ``tuple<_is_ok : bool; _value : T; _error : E>`` — a structural +//! type with no module home, so ``Result`` reached through any chain of +//! ``require ... public`` collapses to one canonical ``tTuple`` candidate +//! (issue #2598). When ``is_ok`` is ``true``, ``value`` is meaningful and +//! ``error`` holds the zero value of ``E``; when ``is_ok`` is ``false``, +//! ``error`` is meaningful and ``value`` holds the zero value of ``T``. Prefer +//! the accessor functions below over direct field reads. +[template_tuple(T, E)] struct template Result { _is_ok : bool = false - @safe_when_uninitialized - _value : T - @safe_when_uninitialized - _error : E + @safe_when_uninitialized _value : T + @safe_when_uninitialized _error : E } //! Construct ``Result`` carrying successful ``v``. Pass the error type as a witness:: @@ -64,8 +65,7 @@ def ok(v : auto(TT); etype : auto(EE)) { //! payloads where ``ok`` would clone. [template(etype)] def move_ok(var v : auto(TT); etype : auto(EE)) { - typedef RT = $Result < TT - const; EE - const > - return <- RT(_is_ok = true, _value <- v) + return <- (_is_ok = true, _value <- v, _error = default) } //! Construct ``Result`` carrying error ``e``. Pass the value type as a witness:: @@ -93,8 +93,7 @@ def err(e : auto(EE); ttype : auto(TT)) { //! payloads where ``err`` would clone. [template(ttype)] def move_err(var e : auto(EE); ttype : auto(TT)) { - typedef RT = $Result < TT - const; EE - const > - return <- RT(_is_ok = false, _error <- e) + return <- (_is_ok = false, _value = default, _error <- e) } //! ``true`` iff the result is a successful ``ok``. diff --git a/daslib/typemacro_boost.das b/daslib/typemacro_boost.das index 9941ae9a25..4bd6d7e679 100644 --- a/daslib/typemacro_boost.das +++ b/daslib/typemacro_boost.das @@ -555,6 +555,134 @@ class TemplateStructure : AstStructureAnnotation { } } +[structure_macro(name="template_tuple")] +class TemplateTuple : AstStructureAnnotation { + //! Like ``[template_structure]``, but produces a typemacro that returns a + //! named tuple instead of cloning a structure. Result is structural — no + //! module home, no per-consumer cloning. Use for sum-type / discriminated- + //! pair shapes (``Option``, ``Result``) where structural identity across + //! transitive ``require ... public`` is the desired behavior (issue #2598). + //! + //! Field types may be arbitrary type expressions referencing the template + //! arguments — ``T``, ``array``, ``tuple``, etc. The runtime + //! body uses ``apply_template`` (templates_boost) to walk each field's + //! TypeDecl and substitute alias slots with the corresponding typemacro arg. + //! + //! ``@safe_when_uninitialized`` on a source field propagates to the + //! corresponding tuple element's ``TypeDeclFlags.safeWhenUninitialized``, + //! so ``default>`` succeeds even when that field's payload type is + //! itself unsafe-when-uninitialized. The tuple's recursive ``unsafeInit`` + //! walks its element types, so per-element marking gives the same effect + //! as the legacy struct-level ``[safe_when_uninitialized]`` did. + def override apply(var st : StructurePtr; var group : ModuleGroup; args : AnnotationArgumentList; var errors : das_string) : bool { + if (length(args) == 0) { + errors := "expecting at least one template argument name" + return false + } + var argNames : array + var ctxFnArguments : array + for (arg in args) { + if (arg.basicType != Type.tBool) { + errors := "template_tuple only supports type-arg names; got '{arg.name}' of {arg.basicType}" + return false + } + argNames |> push(string(arg.name)) + var argVar <- new Variable(name := string(arg.name), at = st.at) + argVar._type = new TypeDecl(baseType = Type.alias, alias := "TypeDeclPtr") + ctxFnArguments.emplace(argVar) + } + let typeMacroFunctionName = string(st.name) + let nFields = length(st.fields) + // Alias TypeDecl for the source struct — at typemacro-execution time + // ``typeinfo ast_typedecl(type<$t(aliasT)>)`` resolves through the + // module-scoped alias to the source struct, giving us its field list + // (with template aliases like ``T`` / ``array`` preserved). + var aliasT = new TypeDecl(baseType = Type.alias, alias := string(st.name)) + // Per-field argName checks — guard the generic path before the + // recursive unifier runs. + var perFieldArgNameChecks : array + for (i, fld in iter_range(st.fields), st.fields) { + let fname = string(fld.name) + perFieldArgNameChecks |> push(qmacro_block() { + if (passArgument.argNames[$v(i)] != $v(fname)) { + return null + } + }) + } + // Rules-block statements — substitute each template-arg alias + // (``T``, ``E``, ...) with the runtime arg. The same rules drive + // both paths: in the concrete path the runtime arg is a fully + // resolved type (``int``), and in the generic path it's + // ``auto()`` — exactly the binding-slot the recursive + // unifier (``inferGenericType`` / ``updateAliasMap``) needs. + var rulesStmts : array + for (an in argNames) { + rulesStmts |> push(qmacro_expr() { + rules |> replaceTypeWithTypeDecl($v(an), clone_type($i(an))); + }) + } + var typeMacroFunction = qmacro_function(typeMacroFunctionName) $(macroArgument, passArgument : TypeDeclPtr; $a(ctxFnArguments)) : TypeDeclPtr { + if (passArgument != null) { + if (passArgument.baseType != Type.tTuple) { + return null + } + if (length(passArgument.argTypes) != $v(nFields)) { + return null + } + if (length(passArgument.argNames) != $v(nFields)) { + return null + } + $b(perFieldArgNameChecks) + // Build (argument_type, inferred_type) pairs from the source + // struct's fields and let ``infer_template_types`` run the + // standard unifier loop. ``apply_template`` substitutes each + // alias (``T``, ``E``, ...) with the runtime arg — concrete + // type when caller passed one, ``auto()`` in + // generic context. Either form is what + // ``infer_template_types`` expects in ``argument_type``. + var src_type_g = typeinfo ast_typedecl(type<$t(aliasT)>) + var src_g = src_type_g.structType + var template_arguments : array + for (i, fld in iter_range(src_g.fields), src_g.fields) { + var fldType <- clone_type(fld._type) + var fldAuto <- apply_template(fld.at, fldType) <| $(var rules : Template) { + $b(rulesStmts) + } + template_arguments |> push(TypeMacroTemplateArgument( + name = string(fld.name), + argument_type = fldAuto, + inferred_type = clone_type(passArgument.argTypes[i]))) + } + return <- infer_template_types(passArgument, template_arguments) + } + // Concrete path: walk the source struct's fields and substitute + // template aliases with the runtime args via templates_boost's + // existing TypeDecl visitor — handles array, tuple, etc. + var src_type = typeinfo ast_typedecl(type<$t(aliasT)>) + var src = src_type.structType + var tt = new TypeDecl(baseType = Type.tTuple, at = macroArgument.at) + tt.argNames |> resize($v(nFields)) + for (i, fld in iter_range(src.fields), src.fields) { + var fldType <- clone_type(fld._type) + var elt <- apply_template(fld.at, fldType) <| $(var rules : Template) { + $b(rulesStmts) + } + tt.argTypes |> emplace_new <| elt + tt.argNames[i] := string(fld.name) + if (!(find_arg(fld.annotation, "safe_when_uninitialized") is nothing)) { + tt.argTypes[i].flags |= TypeDeclFlags.safeWhenUninitialized + } + } + return <- tt + } + append_annotation(typeMacroFunction, "typemacro_boost", "typemacro_function", []) + if (!(compiling_module() |> add_function(typeMacroFunction))) { + panic("can't add function {typeMacroFunctionName}") + } + return true + } +} + [macro_function] def public is_custom_work_done(structType : Structure?) : bool { //! Returns true if custom work has already been performed on the template structure. diff --git a/dastest/dastest.das b/dastest/dastest.das index 8bd37f6224..c4f0cd8b50 100644 --- a/dastest/dastest.das +++ b/dastest/dastest.das @@ -11,6 +11,7 @@ require daslib/ast_boost require daslib/json_boost require daslib/jobque_boost require daslib/rtti +require daslib/strings_boost require fs require suite @@ -328,6 +329,12 @@ def main() : int { if (!collect_files(inputPaths, files)) { return 1 } + if (!empty(test_args.exclude_files)) { + files |> erase_if() $(f) { + let base = base_name(f) + return (find_index_if(test_args.exclude_files) $(pat) => base |> contains(pat)) >= 0 + } + } timingOutliers = test_args.timing_outliers jsonFilePath = test_args.json_file diff --git a/dastest/dastest_clargs.das b/dastest/dastest_clargs.das index 1f6e07637a..8858d3755f 100644 --- a/dastest/dastest_clargs.das +++ b/dastest/dastest_clargs.das @@ -50,6 +50,10 @@ struct DastestArgs { @clarg_doc = "Path to the folder with scripts or single script to test" test_files : array + @clarg_name = "exclude" + @clarg_doc = "Skip test files whose base name contains this substring (repeatable)" + exclude_files : array + @clarg_doc = "Run top-level tests matching this name prefix" test_names : array diff --git a/include/daScript/ast/ast_typedecl.h b/include/daScript/ast/ast_typedecl.h index ef7a0165bf..8cc4a950fb 100644 --- a/include/daScript/ast/ast_typedecl.h +++ b/include/daScript/ast/ast_typedecl.h @@ -275,6 +275,7 @@ namespace das { bool explicitRef : 1; bool isPrivateAlias : 1; // this is a private alias. only matters in the context of module aliasTypes (for now) bool autoToAlias : 1; // this allows conversion of auto to alias + bool safeWhenUninitialized : 1; // exempt this type from "Uninitialized variable/field is unsafe" — analog of struct-level safe_when_uninitialized for tuples / variants / aliases }; uint32_t flags = 0; }; diff --git a/modules/dasSQLITE/daslib/sqlite_boost.das b/modules/dasSQLITE/daslib/sqlite_boost.das index defdb39c86..0855951c60 100644 --- a/modules/dasSQLITE/daslib/sqlite_boost.das +++ b/modules/dasSQLITE/daslib/sqlite_boost.das @@ -2203,20 +2203,23 @@ def is_option_field_type(td : TypeDeclPtr) : bool { if (td.baseType == Type.typeMacro && length(td.dimExpr) > 0 && (td.dimExpr[0] is ExprConstString)) { return (td.dimExpr[0] as ExprConstString).value == "Option" } - // Post-instantiation form: tStructure named `Option` (the [template_structure] machinery stamps - // the type-arg into the struct name). Templates instantiated downstream show _module=, not - // the original "option" module — match on the name shape instead. - if (td.baseType == Type.tStructure && td.structType != null) { - let sname = string(td.structType.name) - if (sname == "Option" || sname |> starts_with("Option<")) { - return true - } + // Post-instantiation form: structural named tuple ``tuple<_has_value:bool; _value:T>``. + // Option is built by daslib/option's typemacro_function as a structural tuple — no + // module home, no Structure clone. Match the canonical shape. + if (td.baseType == Type.tTuple + && length(td.argTypes) == 2 + && length(td.argNames) == 2 + && td.argNames[0] == "_has_value" + && td.argNames[1] == "_value" + && td.argTypes[0].baseType == Type.tBool) { + return true } return false } def unwrap_option_payload_type(var td : TypeDeclPtr) : TypeDeclPtr { - // Unwrap Option → T (or td unchanged); typeMacro carries T in dimExpr[1], structType in `_value`. + // Unwrap Option → T (or td unchanged); typeMacro carries T in dimExpr[1], the + // structural tuple form carries T in argTypes[1] (the `_value` slot). if (td == null) { return td } @@ -2232,18 +2235,14 @@ def unwrap_option_payload_type(var td : TypeDeclPtr) : TypeDeclPtr { } return clone_type((td.dimExpr[1] as ExprTypeDecl).typeexpr) } - // Post-instantiation form: tStructure named `Option` or `Option`. Mirrors is_option_field_type's - // shape detection — module identity doesn't load-bear once we're inside an Option-shaped struct; - // template instantiation downstream of "option" stamps the callee's _module, not "option". - if (td.baseType == Type.tStructure && td.structType != null) { - let sname = string(td.structType.name) - if (sname == "Option" || sname |> starts_with("Option<")) { - for (f in td.structType.fields) { - if ("{f.name}" == "_value") { - return clone_type(f._type) - } - } - } + // Post-instantiation form: structural named tuple ``tuple<_has_value:bool; _value:T>``. + if (td.baseType == Type.tTuple + && length(td.argTypes) == 2 + && length(td.argNames) == 2 + && td.argNames[0] == "_has_value" + && td.argNames[1] == "_value" + && td.argTypes[0].baseType == Type.tBool) { + return clone_type(td.argTypes[1]) } return td } diff --git a/src/ast/ast_infer_type.cpp b/src/ast/ast_infer_type.cpp index 991b13adc5..d6f95f67b7 100644 --- a/src/ast/ast_infer_type.cpp +++ b/src/ast/ast_infer_type.cpp @@ -4479,16 +4479,26 @@ namespace das { } else { auto clashAlias = findAlias(name); if (clashAlias) { - string extra; - if (verbose) { - auto atClash = clashAlias->getDeclarationLocation(); - if (!atClash.empty()) { - extra = "previously declarated at " + atClash.describe(); + // Silent dedupe: if `assume X : T` redeclares an alias that already + // resolves to the exact same type, treat it as a no-op. This covers + // the auto-injected `assume TT : T` placed at the start of a generic + // instance body when an outer scope (e.g. a typemacro-expanded + // result type carrying ``int aka TT``) already binds TT to T. + if (expr->assumeType && expr->assumeType->isSameType(*clashAlias, + RefMatters::no, ConstMatters::no, TemporaryMatters::no)) { + // fall through — the duplicate is harmless + } else { + string extra; + if (verbose) { + auto atClash = clashAlias->getDeclarationLocation(); + if (!atClash.empty()) { + extra = "previously declared at " + atClash.describe(); + } } + error("can't assume " + name + ", type or alias name is already used", extra, "", + expr->at, CompilationError::already_declared_assume_alias); + return; } - error("can't assume " + name + ", type or alias name is already used", extra, "", - expr->at, CompilationError::already_declared_assume_alias); - return; } if (!expr->assumeType) { error("assume without subexpression must have type", "", "", @@ -4562,7 +4572,18 @@ namespace das { } assume.push_back(AssumeEntry{expr, move(collector.vars)}); } else { - assumeType.emplace_back(expr); + // Skip recording when preVisit silently deduped a same-type + // re-assume — keeps assumeType free of self-shadowing duplicates + // that would otherwise add findAlias lookup cost and confuse + // diagnostics. + auto existing = findAlias(expr->alias); + if (existing && expr->assumeType && expr->assumeType->isSameType(*existing, + RefMatters::no, ConstMatters::no, TemporaryMatters::no)) { + // already bound to the same type — preVisit let it through, + // and we leave assumeType untouched so the original wins. + } else { + assumeType.emplace_back(expr); + } } return expr; } diff --git a/src/ast/ast_typedecl.cpp b/src/ast/ast_typedecl.cpp index 852c0ef648..2414f5df9b 100644 --- a/src/ast/ast_typedecl.cpp +++ b/src/ast/ast_typedecl.cpp @@ -220,6 +220,7 @@ namespace das TT->ref = (TT->ref || autoT->ref) && !autoT->removeRef && !TT->removeRef; TT->constant = (TT->constant || autoT->constant) && !autoT->removeConstant && !TT->removeConstant; TT->temporary = (TT->temporary || autoT->temporary) && !autoT->removeTemporary && !TT->removeTemporary; + TT->safeWhenUninitialized = TT->safeWhenUninitialized || autoT->safeWhenUninitialized; if ( (autoT->removeDim || TT->removeDim) && TT->dim.size() ) TT->dim.erase(TT->dim.begin()); TT->removeConstant = false; TT->removeDim = false; @@ -1260,6 +1261,7 @@ namespace das } bool TypeDecl::unsafeInit( das_set & dep ) const { + if ( safeWhenUninitialized ) return false; if ( baseType==Type::tHandle ) { return annotation->hasNonTrivialCtor(); } if ( baseType==Type::tStructure ) { diff --git a/src/ast/ast_unused.cpp b/src/ast/ast_unused.cpp index 22d835404e..87563ac325 100644 --- a/src/ast/ast_unused.cpp +++ b/src/ast/ast_unused.cpp @@ -568,6 +568,12 @@ namespace das { if ( expr->func && expr->func->name == "finalize" && expr->arguments.size()==1 ) { propagateWrite(expr->arguments[0]); } + // call with no resolved function — nothing to propagate; the typer + // either left this as an unresolved generic (error elsewhere) or it's + // a desugar artifact carrying no side-effect contract. + if ( !expr->func ) { + return; + } // if modified, modify NEW auto sef = getSideEffects(expr->func); if ( sef & uint32_t(SideEffects::modifyArgument) ) { diff --git a/src/builtin/module_builtin_ast_flags.cpp b/src/builtin/module_builtin_ast_flags.cpp index 55531e2267..1d7cb7ff1d 100644 --- a/src/builtin/module_builtin_ast_flags.cpp +++ b/src/builtin/module_builtin_ast_flags.cpp @@ -157,7 +157,7 @@ namespace das { "removeRef", "removeConstant", "removeDim", "removeTemporary", "explicitConst", "aotAlias", "smartPtr", "smartPtrNative", "isExplicit", "isNativeDim", "isTag", "explicitRef", - "isPrivateAlias", "autoToAlias" }; + "isPrivateAlias", "autoToAlias", "safeWhenUninitialized" }; return ft; } diff --git a/tests/option/_test_option_user_struct_mod.das b/tests/option/_test_option_user_struct_mod.das new file mode 100644 index 0000000000..a18f59af8a --- /dev/null +++ b/tests/option/_test_option_user_struct_mod.das @@ -0,0 +1,28 @@ +options gen2 + +module _test_option_user_struct_mod shared public + +require daslib/option public + +struct public Foo { + id : int + name : string +} + +def public make_foo(id : int; name : string) : Foo { + return Foo(id = id, name = name) +} + +def public operator ==(a, b : Foo) : bool { + return a.id == b.id && a.name == b.name +} + +// Cross-module Option path. Exercises the bracket-aware +// splitTypeName fix from feedback_option_cross_module_template_bug, and +// guards against re-introducing it under the typemacro+tuple shape. +def public lookup_foo(id : int) : Option { + if (id <= 0) { + return none(type) + } + return some(make_foo(id, "name-{id}")) +} diff --git a/tests/option/_test_template_tuple_composite_mod.das b/tests/option/_test_template_tuple_composite_mod.das new file mode 100644 index 0000000000..1e5ff2c87b --- /dev/null +++ b/tests/option/_test_template_tuple_composite_mod.das @@ -0,0 +1,46 @@ +options gen2 +options indenting = 4 + +module _test_template_tuple_composite_mod shared public + +require daslib/typemacro_boost public + +// ``Bag`` — single-arg, composite field shape (``items : array``). +// Pre-fix, the typemacro generic path failed to match ``$Bag`` +// against a concrete ``Bag`` because the per-field detection only +// caught direct alias and the bind-extraction was wrong-shaped for any +// wrapper. +[template_tuple(T)] +struct template public Bag { + count : int = 0 + items : array +} + +// ``Pair`` — multi-arg, top-level alias fields. +[template_tuple(K, V)] +struct template public Pair { + key : K + value : V +} + +// ``KVList`` — multi-arg, composite field shapes for both args. +[template_tuple(K, V)] +struct template public KVList { + keys : array + values : array +} + +// Generic-path consumers — these are the call sites that were broken +// pre-fix. Resolution invokes the typemacro's generic path and binds +// the auto-named template arg through the field shape. +def public bag_size(b : $Bag < auto(TT) >) : int { + return length(b.items) +} + +def public pair_first(p : $Pair < auto(KK), auto(VV) >) : KK { + return p.key +} + +def public kvlist_size(l : $KVList < auto(KK), auto(VV) >) : int { + return length(l.keys) +} diff --git a/tests/option/test_option_unsafe_uninitialized.das b/tests/option/test_option_unsafe_uninitialized.das new file mode 100644 index 0000000000..57602128ce --- /dev/null +++ b/tests/option/test_option_unsafe_uninitialized.das @@ -0,0 +1,53 @@ +options gen2 +options indenting = 4 +options no_unused_block_arguments = false +options no_unused_function_arguments = false + +require dastest/testing_boost public +require daslib/option + +// Regression for typemacro+tuple Option carrying a payload that's +// "unsafe-when-uninitialized" — i.e. a struct with hasInitFields = true. +// Without TypeDeclFlags.safeWhenUninitialized on the value field, +// default> would trip ast_infer_type.cpp:5032's +// "Uninitialized variable" check. + +struct Brittle { + must_init : int = 42 + name : string = "default" +} + +[test] +def option_brittle_constructors(t : T?) { + t |> run("default> compiles and reads as none") @(t : T?) { + let o = none(type) + t |> success(o |> is_none) + t |> success(!(o |> is_some)) + } + t |> run("bare var Option compiles via @safe_when_uninitialized on _value") @(t : T?) { + // Without @safe_when_uninitialized on the _value field of the tuple + // produced by [template_tuple(T)] in daslib/option.das, this would + // trigger "Uninitialized variable is unsafe" at ast_infer_type.cpp:5040. + // The bare var (no init) is the only form that exercises this check — + // ``let`` requires an initializer. + var bare : Option // nolint:LINT003 + t |> success(bare |> is_none) + } + t |> run("some(Brittle(...)) round-trips") @(t : T?) { + let o = some(Brittle(must_init = 7, name = "x")) + t |> success(o |> is_some) + let v = o |> unwrap + t |> equal(v.must_init, 7) + t |> equal(v.name, "x") + } +} + +[test] +def option_brittle_unwrap_or_default(t : T?) { + t |> run("unwrap_or_default returns default on none") @(t : T?) { + let o = none(type) + let v = o |> unwrap_or_default + t |> equal(v.must_init, 42) + t |> equal(v.name, "default") + } +} diff --git a/tests/option/test_option_user_struct.das b/tests/option/test_option_user_struct.das new file mode 100644 index 0000000000..e093d05484 --- /dev/null +++ b/tests/option/test_option_user_struct.das @@ -0,0 +1,42 @@ +options gen2 +options indenting = 4 +options no_unused_block_arguments = false +options no_unused_function_arguments = false + +require dastest/testing_boost public +require daslib/option +require _test_option_user_struct_mod + +// Regression for feedback_option_cross_module_template_bug.md (splitTypeName +// must be bracket-aware so Option<_mod::UserStruct> doesn't split at the +// inner ::). With Option as a structural tuple (typemacro_function), tuples +// are not module-keyed, so this should be even more robust — the test exists +// to prove that conclusion under future refactors. + +[test] +def cross_module_option_some(t : T?) { + t |> run("Option across module boundary — some path") @(t : T?) { + let hit = lookup_foo(7) + t |> success(hit |> is_some) + let v = hit |> unwrap + t |> equal(v.id, 7) + t |> equal(v.name, "name-7") + } +} + +[test] +def cross_module_option_none(t : T?) { + t |> run("Option across module boundary — none path") @(t : T?) { + let miss = lookup_foo(0) + t |> success(miss |> is_none) + } +} + +[test] +def cross_module_option_equality(t : T?) { + t |> run("Option structural equality across module boundary") @(t : T?) { + t |> success(lookup_foo(3) == some(make_foo(3, "name-3"))) + t |> success(!(lookup_foo(0) == some(make_foo(3, "name-3")))) + t |> success(!(lookup_foo(3) == some(make_foo(4, "name-4")))) + } +} diff --git a/tests/option/test_template_tuple_composite.das b/tests/option/test_template_tuple_composite.das new file mode 100644 index 0000000000..dd9d84a561 --- /dev/null +++ b/tests/option/test_template_tuple_composite.das @@ -0,0 +1,82 @@ +options gen2 +options indenting = 4 +options no_unused_block_arguments = false +options no_unused_function_arguments = false + +require dastest/testing_boost public +require _test_template_tuple_composite_mod + +// Regression coverage for ``[template_tuple]`` field shapes with template +// args INSIDE a wrapper (``array``, etc.). Production ``Option`` / +// ``Result`` use only top-level-alias fields, so they did not exercise +// the generic-path bind for composite shapes. See the helper module for +// details on what was broken pre-fix. + +[test] +def template_tuple_composite_single_arg(t : T?) { + t |> run("Bag: default + concrete construction") @(t : T?) { + var b = default> + t |> equal(b.count, 0) + t |> equal(length(b.items), 0) + } + t |> run("Bag: move payload, read fields") @(t : T?) { + var items : array + items |> push(1) + items |> push(2) + items |> push(3) + var b : Bag + b.count = length(items) + b.items <- items + t |> equal(b.count, 3) + t |> equal(length(b.items), 3) + t |> equal(b.items[1], 2) + } + t |> run("Bag: generic dispatch infers TT through array") @(t : T?) { + var b : Bag + b.items |> push("a") + b.items |> push("b") + t |> equal(bag_size(b), 2) + } +} + +[test] +def template_tuple_top_level_multi_arg(t : T?) { + t |> run("Pair: concrete construction") @(t : T?) { + var p : Pair + p.key = 7 + p.value = "x" + t |> equal(p.key, 7) + t |> equal(p.value, "x") + } + t |> run("Pair: generic dispatch infers KK + VV") @(t : T?) { + var p : Pair + p.key = 42 + p.value = "y" + t |> equal(pair_first(p), 42) + } +} + +[test] +def template_tuple_composite_multi_arg(t : T?) { + t |> run("KVList: concrete construction") @(t : T?) { + var l : KVList + l.keys |> push(1) + l.keys |> push(2) + l.values |> push("a") + l.values |> push("b") + t |> equal(length(l.keys), 2) + t |> equal(length(l.values), 2) + t |> equal(l.keys[0], 1) + t |> equal(l.values[1], "b") + } + t |> run("KVList: generic dispatch through both array and array") @(t : T?) { + var l : KVList + l.keys |> push(10) + l.keys |> push(20) + l.keys |> push(30) + l.values |> push("p") + l.values |> push("q") + l.values |> push("r") + t |> equal(kvlist_size(l), 3) + } +} diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000000..2fe124cd72 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,27 @@ +# Build output +output/ + +# CMake build trees (in-source or cmake_temp/) +CMakeFiles/ +CMakeCache.txt +CopyOfCMakeCache.txt +cmake_install.cmake +cmake_temp/ +cmake/ +daslang_src/ + +# Ninja +build.ninja +.ninja_deps +.ninja_log + +# Fetched/built dependencies +libglfw_static/ +libglfw_dyn/ +emsdk/ + +# Generated include files +include/modules/ + +# Misc cmake artifacts +CMakeXxdImpl.txt diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index 03cdb357f9..b606531cc8 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -29,659 +29,38 @@ add_compile_definitions(_EMSCRIPTEN_VER) add_link_options( # Default STACK_SIZE is 64KB, not enough for daslang. -sSTACK_SIZE=4MB - # `callMain` should be exported. - -sEXPORTED_RUNTIME_METHODS=callMain + -sEXPORTED_RUNTIME_METHODS=callMain,FS # INVOKE_RUN needed to disable running daslang on initialization # (it will print help message and exit). -sINVOKE_RUN=0 + # NODEFS required for mounting the host filesystem in Node.js tests. + -sFORCE_FILESYSTEM=1 + -lnodefs.js + # Allow heap to grow beyond the default 16MB cap. + -sALLOW_MEMORY_GROWTH=1 ) # embed daslang standard library into wasm binary. add_link_options(--embed-file ${CMAKE_CURRENT_SOURCE_DIR}/../daslib@daslib) -INCLUDE(../CMakeCommon.txt) - IF(DEFINED DAS_CONFIG_INCLUDE_DIR) INCLUDE_DIRECTORIES(${DAS_CONFIG_INCLUDE_DIR}) ENDIF() set_property(GLOBAL PROPERTY USE_FOLDERS ON) -set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/output/) - -SET(LINUX_UUID FALSE) - -IF(NOT DAS_FLEX_BISON_DISABLED) - FIND_FLEX_AND_BISON() -ENDIF() - -SETUP_COMPILER() - -set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) - -MACRO(ADD_STANDALONE_FILE genList input) - get_filename_component(input_src ${input} ABSOLUTE) - get_filename_component(input_dir ${input_src} DIRECTORY) - get_filename_component(input_name ${input} NAME) - set(out_dir ${input_dir}/_standalone_ctx_generated) - set(out_inc ${out_dir}/${input_name}.h) - set(out_src ${out_dir}/${input_name}.cpp) - set_source_files_properties(${out_inc} ${out_src} PROPERTIES GENERATED TRUE) - list(APPEND ${genList} ${out_inc} ${out_src}) -ENDMACRO() - -MACRO (ADD_AOT_EXT_FILE genList mainTarget input) - get_filename_component(input_src ${input} ABSOLUTE) - get_filename_component(input_dir ${input_src} DIRECTORY) - get_filename_component(input_name ${input} NAME) - set(out_dir ${input_dir}/_aot_generated) - set(out_src "${out_dir}/${mainTarget}_${input_name}.cpp") - set_source_files_properties(${out_src} PROPERTIES GENERATED TRUE) - list(APPEND ${genList} ${out_src}) -ENDMACRO() - - -MACRO(UNITIZE_BUILD input_dir genList) - set(unitList) - set(files ${${genList}}) - set(out_dir "${PROJECT_SOURCE_DIR}/${input_dir}/_aot_generated") - set_source_files_properties(${files} PROPERTIES HEADER_FILE_ONLY true) - set(unit_build_file "") - set(file_index 0) - set(batch_index 0) - foreach(source_file ${files} ) - if(file_index EQUAL "0") - set(unit_build_file "${out_dir}/unity_build_${batch_index}.cpp") - FILE(WRITE ${unit_build_file} "// unity build batch ${batch_index}\n") - list(APPEND unitList ${unit_build_file}) - endif() - get_filename_component(input_name ${source_file} NAME) - FILE(APPEND ${unit_build_file} "#include \"${input_name}\"\n") - math(EXPR file_index "${file_index} + 1") - if(file_index EQUAL ${UNITZE_BUILD_BATCH_SIZE}) - set(file_index 0) - math(EXPR batch_index "${batch_index} + 1") - endif() - endforeach() - list(APPEND ${genList} ${unitList}) -ENDMACRO() - -FIND_PROGRAM(CLANG_BIN_EXE clang) -IF(CLANG_BIN_EXE) - MACRO(DAS_CPP_BIND_AST tgt generate_das header_from prefix src_dir inc_dirs extras) - MESSAGE(STATUS "Generating C++ bindings target ${tgt} from ${header_from}") - ADD_CUSTOM_TARGET(${tgt}) - ADD_DEPENDENCIES(${tgt} daScript) - ADD_CUSTOM_COMMAND( - TARGET ${tgt} - WORKING_DIRECTORY ${src_dir} - VERBATIM - COMMAND ${CLANG_BIN_EXE} -Xclang -ast-dump=json -x c++ -c ${header_from} -I${inc_dirs} > ${header_from}.json 2> ${header_from}.json.log || cmd /c exit /b 0 - COMMENT "Generating JSON AST for ${header_from} to ${header_from}.json\ninclude directoryes are ${inc_dirs}\n" - ) - ADD_CUSTOM_COMMAND( - TARGET ${tgt} - WORKING_DIRECTORY ${src_dir} - VERBATIM - COMMAND daScript ${generate_das} -args ${header_from}.json ${prefix} "${extras}" - COMMENT "Generating C++ bindings from ${header_from}.json\n" - ) - ENDMACRO() - - MACRO(DAS_OUTPUT_AST tgt header_from src_dir inc_dirs) - MESSAGE(STATUS "Generating C++ bindings target ${tgt} from ${header_from}") - ADD_CUSTOM_TARGET(${tgt}) - ADD_DEPENDENCIES(${tgt} daScript) - ADD_CUSTOM_COMMAND( - TARGET ${tgt} - WORKING_DIRECTORY ${src_dir} - VERBATIM - COMMAND ${CLANG_BIN_EXE} -Xclang -ast-dump -x c++ -c ${header_from} -I${inc_dirs} > ${header_from}.ast 2> ${header_from}.ast.log || cmd /c exit /b 0 - COMMENT "Generating JSON AST for ${header_from} to ${header_from}.json\ninclude directoryes are ${inc_dirs}\n" - ) - ENDMACRO() - - MACRO(DAS_OUTPUT_JSON tgt header_from src_dir inc_dirs) - MESSAGE(STATUS "Generating C++ bindings target ${tgt} from ${header_from}") - ADD_CUSTOM_TARGET(${tgt}) - ADD_DEPENDENCIES(${tgt} daScript) - ADD_CUSTOM_COMMAND( - TARGET ${tgt} - WORKING_DIRECTORY ${src_dir} - VERBATIM - COMMAND ${CLANG_BIN_EXE} -Xclang -ast-dump=json -x c++ -c ${header_from} -I${inc_dirs} > ${header_from}.ast 2> ${header_from}.ast.log || cmd /c exit /b 0 - COMMENT "Generating JSON AST for ${header_from} to ${header_from}.json\ninclude directoryes are ${inc_dirs}\n" - ) - ENDMACRO() - - -ELSE() - MACRO(DAS_CPP_BIND_AST tgt generate_das header_from prefix src_dir inc_dirs) - MESSAGE(STATUS "Skipping C++ bindings from ${header_from}") - ENDMACRO() - - MACRO(DAS_OUTPUT_AST tgt header_from src_dir inc_dirs) - MESSAGE(STATUS "Skipping ast dump from from ${header_from}") - ENDMACRO() - -ENDIF() - -SET(DAS_MODULES_RESOLVE_INC ${PROJECT_SOURCE_DIR}/../include/modules/external_resolve.inc) -SET(DAS_MODULES_NEED_INC ${PROJECT_SOURCE_DIR}/../include/modules/external_need.inc) -SET(DAS_MODULES_DECLARE_INC ${PROJECT_SOURCE_DIR}/../include/modules/external_declare.inc) -SET(DAS_MODULES_PULL_INC ${PROJECT_SOURCE_DIR}/../include/modules/external_pull.inc) -FILE(WRITE ${DAS_MODULES_RESOLVE_INC}.temp "") -FILE(WRITE ${DAS_MODULES_NEED_INC}.temp "") -FILE(WRITE ${DAS_MODULES_DECLARE_INC}.temp "") -FILE(WRITE ${DAS_MODULES_PULL_INC}.temp "") - -SET(DAS_MODULES_LIBS) -SET(DAS_ALL_EXAMPLES) -FILE(GLOB _modules RELATIVE "${PROJECT_SOURCE_DIR}/modules" "modules/*") -FOREACH(_module ${_modules}) - MACRO(ADD_MODULE_LIB lib) - MESSAGE("REGISTER DAS MODULE ${lib}") - LIST(APPEND DAS_MODULES_LIBS ${lib}) - ENDMACRO() - - MACRO(ADD_EXAMPLE tgt) - MESSAGE("EXAMPLE ${tgt}") - LIST(APPEND DAS_ALL_EXAMPLES ${tgt}) - ENDMACRO() - - FUNCTION(ADD_MODULE_CPP cpp) - FILE(APPEND ${DAS_MODULES_NEED_INC}.temp "NEED_MODULE(Module_${cpp});\n") - FILE(APPEND ${DAS_MODULES_DECLARE_INC}.temp "DECLARE_MODULE(Module_${cpp});\n") - FILE(APPEND ${DAS_MODULES_PULL_INC}.temp "PULL_MODULE(Module_${cpp});\n") - ENDFUNCTION() - - FUNCTION(ADD_MODULE_NATIVE native) - FILE(APPEND ${DAS_MODULES_RESOLVE_INC}.temp "NATIVE_MODULE(\"daslib\",\"daslib\", ${_module}, ${native});\n") - ENDFUNCTION() - - FUNCTION(ADD_MODULE_DAS category subfolder native) - FILE(APPEND ${DAS_MODULES_RESOLVE_INC}.temp "NATIVE_MODULE(${category}, ${subfolder}, ${_module}, ${native});\n") - ENDFUNCTION() +set(DAS_WEB_OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/output + CACHE PATH "Directory where daslang.wasm and web UI assets are written") +add_subdirectory(.. ${CMAKE_BINARY_DIR}/daslang_src EXCLUDE_FROM_ALL) +# All we need in web is daslang_static.wasm. +set_target_properties(daslang_static PROPERTIES + EXCLUDE_FROM_ALL FALSE + RUNTIME_OUTPUT_DIRECTORY ${DAS_WEB_OUTPUT_DIR}) - #INCLUDE(modules/${_module}/CMakeLists.txt OPTIONAL) -ENDFOREACH() - -FOREACH(_example ${DAS_ALL_EXAMPLES}) - MESSAGE("adding to ${_example} ${DAS_MODULES_LIBS}") - TARGET_LINK_LIBRARIES(${_example} ${DAS_MODULES_LIBS}) - ADD_DEPENDENCIES(${_example} ${DAS_MODULES_LIBS}) -ENDFOREACH() - -ADD_CUSTOM_COMMAND( - DEPENDS ${DAS_MODULES_NEED_INC}.temp - OUTPUT ${DAS_MODULES_NEED_INC} - VERBATIM - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DAS_MODULES_NEED_INC}.temp ${DAS_MODULES_NEED_INC} -) - -ADD_CUSTOM_COMMAND( - DEPENDS ${DAS_MODULES_RESOLVE_INC}.temp - OUTPUT ${DAS_MODULES_RESOLVE_INC} - VERBATIM - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DAS_MODULES_RESOLVE_INC}.temp ${DAS_MODULES_RESOLVE_INC} -) - -ADD_CUSTOM_COMMAND( - DEPENDS ${DAS_MODULES_DECLARE_INC}.temp - OUTPUT ${DAS_MODULES_DECLARE_INC} - VERBATIM - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DAS_MODULES_DECLARE_INC}.temp ${DAS_MODULES_DECLARE_INC} -) - -ADD_CUSTOM_COMMAND( - DEPENDS ${DAS_MODULES_PULL_INC}.temp - OUTPUT ${DAS_MODULES_PULL_INC} - VERBATIM - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DAS_MODULES_PULL_INC}.temp ${DAS_MODULES_PULL_INC} -) - -ADD_CUSTOM_TARGET(need_and_resolve ALL DEPENDS ${DAS_MODULES_NEED_INC} ${DAS_MODULES_RESOLVE_INC} ${DAS_MODULES_DECLARE_INC} ${DAS_MODULES_PULL_INC}) - -# libUriParser - -SET(URIPARSER_SRCS -../3rdparty/uriparser/src/UriCommon.c -../3rdparty/uriparser/src/UriCompare.c -../3rdparty/uriparser/src/UriEscape.c -../3rdparty/uriparser/src/UriFile.c -../3rdparty/uriparser/src/UriIp4.c -../3rdparty/uriparser/src/UriIp4Base.c -../3rdparty/uriparser/src/UriNormalize.c -../3rdparty/uriparser/src/UriNormalizeBase.c -../3rdparty/uriparser/src/UriParse.c -../3rdparty/uriparser/src/UriParseBase.c -../3rdparty/uriparser/src/UriQuery.c -../3rdparty/uriparser/src/UriRecompose.c -../3rdparty/uriparser/src/UriResolve.c -../3rdparty/uriparser/src/UriShorten.c -../3rdparty/uriparser/src/UriMemory.c +# Copy web UI assets to output at build time. +add_custom_target(copy_web_ui ALL + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/ui + ${DAS_WEB_OUTPUT_DIR} + COMMENT "Copying web UI to ${DAS_WEB_OUTPUT_DIR}" ) - -ADD_LIBRARY(libUriParser STATIC ${URIPARSER_SRCS}) -SETUP_CPP11(libUriParser) -target_compile_definitions(libUriParser PUBLIC URIPARSER_BUILD_CHAR) -target_compile_definitions(libUriParser PUBLIC URI_STATIC_BUILD) -target_include_directories(libUriParser PUBLIC - ${PROJECT_SOURCE_DIR}/../3rdparty/uriparser/include -) - -# libDaScript - -FLEX(../src/parser/ds_lexer.lpp) -BISON(../src/parser/ds_parser.ypp) - -SET(PARSER_GENERATED_SRC -# parser 1 -../src/parser/ds_parser.hpp -../src/parser/ds_parser.cpp -../src/parser/ds_lexer.cpp -../src/parser/lex.yy.h -# parser 2 -../src/parser/ds2_parser.hpp -../src/parser/ds2_parser.cpp -../src/parser/ds2_lexer.cpp -../src/parser/lex2.yy.h -) - -SET(PARSER_SRC -../src/parser/ds_parser.ypp -../src/parser/ds_lexer.lpp -../src/parser/ds2_parser.ypp -../src/parser/ds2_lexer.lpp -../src/parser/parser_state.h -../src/parser/parser_impl.cpp -../src/parser/parser_impl.h -) - - -list(SORT PARSER_SRC) -SOURCE_GROUP_FILES("parser" PARSER_SRC) -SOURCE_GROUP_FILES("parser/generated" PARSER_GENERATED_SRC) - -IF(MSVC) - # Don't use precompiled headers with flex/bison generated files - FOREACH(file ${PARSER_GENERATED_SRC}) - SET_SOURCE_FILES_PROPERTIES(${file} PROPERTIES COMPILE_FLAGS "/Y-" ) - ENDFOREACH() -ENDIF() - -SET(VECMATH_SRC -../include/vecmath/dag_vecMath.h -../include/vecmath/dag_vecMathDecl.h -../include/vecmath/dag_vecMath_common.h -../include/vecmath/dag_vecMath_const.h -../include/vecmath/dag_vecMath_neon.h -../include/vecmath/dag_vecMath_pc_sse.h -) -list(SORT VECMATH_SRC) -SOURCE_GROUP_FILES("vecmath" VECMATH_SRC) - -SET(AST_SRC -../src/ast/ast.cpp -../src/ast/ast_dispatch.cpp -../src/ast/ast_interop.cpp -../src/ast/ast_tls.cpp -../src/ast/ast_visitor.cpp -../src/ast/ast_generate.cpp -../src/ast/ast_simulate.cpp -../src/ast/ast_typedecl.cpp -../src/ast/ast_match.cpp -../src/ast/ast_module.cpp -../src/ast/ast_print.cpp -../src/ast/ast_aot_cpp.cpp -../src/ast/ast_infer_type.cpp -../src/ast/ast_infer_type_report.cpp -../src/ast/ast_infer_type_function.cpp -../src/ast/ast_infer_type_helper.cpp -../src/ast/ast_infer_type_op.cpp -../src/ast/ast_infer_type_make.cpp -../src/ast/ast_lint.cpp -../src/ast/ast_allocate_stack.cpp -../src/ast/ast_derive_alias.cpp -../src/ast/ast_const_folding.cpp -../src/ast/ast_block_folding.cpp -../src/ast/ast_inscope_pod.cpp -../src/ast/ast_gc_collect.cpp -../src/ast/ast_unused.cpp -../src/ast/ast_annotations.cpp -../src/ast/ast_export.cpp -../src/ast/ast_parse.cpp -../src/ast/ast_validate.cpp -../src/ast/ast_debug_info_helper.cpp -../src/ast/ast_handle.cpp -../include/daScript/ast/compilation_errors.h -../include/daScript/ast/ast_typedecl.h -../include/daScript/ast/ast_typefactory.h -../include/daScript/ast/ast.h -../include/daScript/ast/ast_expressions.h -../include/daScript/ast/ast_visitor.h -../include/daScript/ast/ast_generate.h -../include/daScript/ast/ast_match.h -../include/daScript/ast/ast_interop.h -../include/daScript/ast/ast_handle.h -../include/daScript/ast/ast_policy_types.h -) -list(SORT AST_SRC) -SOURCE_GROUP_FILES("ast" AST_SRC) - -SET(BUILTIN_SRC -../include/daScript/builtin/ast_gen.inc -../include/daScript/builtin/debugapi_gen.inc - - -../src/builtin/modules.cpp -../src/builtin/module_builtin.h -../src/builtin/module_builtin.cpp -../src/builtin/module_builtin_misc_types.cpp -../src/builtin/module_builtin_runtime.cpp -../src/builtin/module_builtin_runtime_sort.cpp -../src/builtin/module_builtin_runtime_lockcheck.cpp -../src/builtin/module_builtin_vector.cpp -../src/builtin/module_builtin_vector_ctor.cpp -../src/builtin/module_builtin_array.cpp -../src/builtin/module_builtin_math.cpp -../src/builtin/module_builtin_string.cpp -../src/builtin/module_builtin_rtti.h -../src/builtin/module_builtin_rtti.cpp -../src/builtin/module_builtin_ast.cpp -../src/builtin/module_builtin_ast_serialize.cpp -../src/builtin/module_builtin_ast_flags.cpp -../src/builtin/module_builtin_ast_annotations.cpp -../src/builtin/module_builtin_ast_annotations_1.cpp -../src/builtin/module_builtin_ast_annotations_2.cpp -../src/builtin/module_builtin_ast_annotations_3.cpp -../src/builtin/module_builtin_ast_adapters.cpp -../src/builtin/module_builtin_ast.h -../src/builtin/module_builtin_uriparser.h -../src/builtin/module_builtin_uriparser.cpp -../src/builtin/module_jit.cpp -../src/builtin/module_builtin_fio.cpp -../src/builtin/module_builtin_dasbind.cpp -../src/builtin/module_builtin_network.cpp -../src/builtin/module_builtin_debugger.cpp -../src/builtin/module_builtin_jobque.cpp -../src/builtin/module_file_access.cpp -) -list(SORT BUILTIN_SRC) -SOURCE_GROUP_FILES("module builtin" BUILTIN_SRC) - -SET(MISC_SRC -../include/daScript/misc/enums.h -../include/daScript/misc/hal.h -../include/daScript/misc/fpe.h -../include/daScript/misc/platform.h -../include/daScript/misc/vectypes.h -../include/daScript/misc/arraytype.h -../include/daScript/misc/rangetype.h -../include/daScript/misc/string_writer.h -../include/daScript/misc/type_name.h -../include/daScript/misc/memory_model.h -../include/daScript/misc/wyhash.h -../include/daScript/misc/anyhash.h -../include/daScript/misc/safebox.h -../include/daScript/misc/smart_ptr.h -../include/daScript/misc/free_list.h -../include/daScript/misc/sysos.h -../include/daScript/misc/callable.h -../include/daScript/misc/debug_break.h -../include/daScript/misc/instance_debugger.h -../include/daScript/misc/gc_node.h -../include/daScript/misc/job_que.h -../include/daScript/misc/uric.h -../src/misc/sysos.cpp -../src/misc/string_writer.cpp -../src/misc/memory_model.cpp -../src/misc/job_que.cpp -../src/misc/free_list.cpp -../src/misc/gc_node.cpp -../src/misc/globals.cpp -../src/misc/daScriptC.cpp -../src/misc/handle_registry.cpp -../src/misc/uric.cpp -../src/misc/format.cpp -) -list(SORT MISC_SRC) -SOURCE_GROUP_FILES("misc" MISC_SRC) - -SET(SIMULATE_FUSION_SRC -../src/simulate/simulate_fusion.cpp -../src/simulate/simulate_fusion_op1.cpp -../src/simulate/simulate_fusion_op1_return.cpp -../src/simulate/simulate_fusion_ptrfdr.cpp -../src/simulate/simulate_fusion_op2.cpp -../src/simulate/simulate_fusion_op2_set.cpp -../src/simulate/simulate_fusion_op2_bool.cpp -../src/simulate/simulate_fusion_op2_bin.cpp -../src/simulate/simulate_fusion_op2_vec.cpp -../src/simulate/simulate_fusion_op2_set_vec.cpp -../src/simulate/simulate_fusion_op2_bool_vec.cpp -../src/simulate/simulate_fusion_op2_bin_vec.cpp -../src/simulate/simulate_fusion_op2_scalar_vec.cpp -../src/simulate/simulate_fusion_at.cpp -../src/simulate/simulate_fusion_at_array.cpp -../src/simulate/simulate_fusion_tableindex.cpp -../src/simulate/simulate_fusion_misc_copy.cpp -../src/simulate/simulate_fusion_call1.cpp -../src/simulate/simulate_fusion_call2.cpp -../src/simulate/simulate_fusion_if.cpp -../include/daScript/simulate/simulate_fusion.h -../include/daScript/simulate/simulate_fusion_op1.h -../include/daScript/simulate/simulate_fusion_op1_impl.h -../include/daScript/simulate/simulate_fusion_op1_perm.h -../include/daScript/simulate/simulate_fusion_op1_set_impl.h -../include/daScript/simulate/simulate_fusion_op1_set_perm.h -../include/daScript/simulate/simulate_fusion_op1_reg.h -../include/daScript/simulate/simulate_fusion_op2.h -../include/daScript/simulate/simulate_fusion_op2_impl.h -../include/daScript/simulate/simulate_fusion_op2_perm.h -../include/daScript/simulate/simulate_fusion_op2_set_impl.h -../include/daScript/simulate/simulate_fusion_op2_set_perm.h -../include/daScript/simulate/simulate_fusion_op2_vec_settings.h -) -list(SORT SIMULATE_FUSION_SRC) -SOURCE_GROUP_FILES("fusion" SIMULATE_FUSION_SRC) - -SET(SIMULATE_SRC -../src/hal/performance_time.cpp -../include/daScript/misc/performance_time.h -../src/hal/debug_break.cpp -../src/hal/crash_handler.cpp -../src/hal/project_specific.cpp -../src/hal/project_specific_file_info.cpp -../src/hal/project_specific_crash_handler.cpp -../include/daScript/misc/crash_handler.h -../include/daScript/misc/network.h -../src/misc/network.cpp -../src/simulate/hash.cpp -../src/simulate/debug_info.cpp -../src/simulate/runtime_string.cpp -../src/simulate/runtime_array.cpp -../src/simulate/runtime_table.cpp -../src/simulate/runtime_profile.cpp -../src/simulate/standalone_ctx_utils.cpp -../src/simulate/simulate.cpp -../src/simulate/simulate_exceptions.cpp -../src/simulate/simulate_gc.cpp -../src/simulate/simulate_tracking.cpp -../src/simulate/simulate_visit.cpp -../src/simulate/simulate_print.cpp -../src/simulate/simulate_fn_hash.cpp -../src/simulate/simulate_instrument.cpp -../include/daScript/simulate/cast.h -../include/daScript/simulate/hash.h -../include/daScript/simulate/heap.h -../src/simulate/heap.cpp -../include/daScript/simulate/debug_info.h -../include/daScript/simulate/interop.h -../include/daScript/simulate/runtime_string.h -../include/daScript/simulate/runtime_string_delete.h -../include/daScript/simulate/runtime_array.h -../include/daScript/simulate/runtime_table.h -../include/daScript/simulate/runtime_table_nodes.h -../include/daScript/simulate/runtime_range.h -../include/daScript/simulate/runtime_profile.h -../include/daScript/simulate/runtime_matrices.h -../include/daScript/simulate/simulate.h -../include/daScript/simulate/simulate_nodes.h -../include/daScript/simulate/simulate_visit.h -../include/daScript/simulate/simulate_visit_op.h -../include/daScript/simulate/simulate_visit_op_undef.h -../include/daScript/simulate/sim_policy.h -../src/simulate/data_walker.cpp -../include/daScript/simulate/data_walker.h -../src/simulate/debug_print.cpp -../src/simulate/json_print.cpp -../src/simulate/json_scan.cpp -../include/daScript/simulate/debug_print.h -../include/daScript/simulate/for_each.h -../include/daScript/simulate/bind_enum.h -../include/daScript/simulate/bin_serializer.h -../src/simulate/bin_serializer.cpp -../include/daScript/simulate/aot.h -../include/daScript/simulate/aot_library.h -../include/daScript/simulate/aot_builtin.h -../include/daScript/simulate/aot_builtin_math.h -../include/daScript/simulate/aot_builtin_matrix.h -../include/daScript/simulate/aot_builtin_time.h -../include/daScript/simulate/aot_builtin_string.h -../include/daScript/simulate/aot_builtin_fio.h -../include/daScript/simulate/aot_builtin_rtti.h -../include/daScript/simulate/aot_builtin_ast.h -../include/daScript/simulate/aot_builtin_debugger.h -../include/daScript/simulate/aot_builtin_jobque.h -../include/daScript/simulate/aot_builtin_dasbind.h -../include/daScript/simulate/aot_builtin_uriparser.h -../include/daScript/simulate/aot_builtin_jit.h -../include/daScript/simulate/fs_file_info.h -../src/simulate/fs_file_info.cpp -${DAS_MODULES_RESOLVE_INC} -) - -SET(DASCRIPT_FMT_SRC -../utils/dasFormatter/fmt.cpp -../utils/dasFormatter/formatter.cpp -../utils/dasFormatter/helpers.cpp -../utils/dasFormatter/ds_parser.cpp -CACHE INTERNAL "DAS_DASCRIPT_FMT_SRC" -) - -SET(AOT_STUB_SRC -../src/misc/aot_stub.cpp -) - - -list(SORT SIMULATE_SRC) -SOURCE_GROUP_FILES("simulate" SIMULATE_SRC) - -SET(DAGOR_NOISE_SRC -../include/dag_noise/dag_uint_noise.h -) -SOURCE_GROUP_FILES("dag_noise" DAGOR_NOISE_SRC) - -SET(DAS_HASH_MAP_SRC -../include/das_hash_map/das_hash_map.h -) -SOURCE_GROUP_FILES("das_hash_map" DAS_HASH_MAP_SRC) - -SET(MAIN_SRC -../include/daScript/daScript.h -../include/daScript/daScriptC.h -../include/daScript/daScriptModule.h -../include/daScript/das_config.h -../include/daScript/das_project_specific.h -) -SOURCE_GROUP_FILES("main" MAIN_SRC) - -file(GLOB DAS_LIB_SRC -"daslib/*.das" -) -list(SORT DAS_LIB_SRC) -SOURCE_GROUP_FILES("daslib" DSA_LIB_SRC) -list(SORT DAS_LIB_SRC) - -add_custom_target(dasAotStub) -SET(AOT_GENERATED_SRC) -# AOT stubs are disabled for WASM builds — the pre-generated stubs in daslib/_aot_generated/ -# are built on x64 (64-bit pointers) and contain sizeof/offsetof static_asserts that fail -# on wasm32 (32-bit pointers). WASM runs interpreter-only. -#set(AotDaslibList -# ../daslib/functional.das -# ../daslib/json.das -# ../daslib/json_boost.das -# ../daslib/ast_boost.das -# ../daslib/templates_boost.das -# ../daslib/utf8_utils.das -# ../daslib/regex.das -# ../daslib/regex_boost.das -# ../daslib/strings_boost.das -# ../daslib/random.das -# ../daslib/math_boost.das -# -# # ../src/das/ast/printer_flags_visitor.das -#) -#SET(AOT_GENERATED_SRC) -#FOREACH(inF IN LISTS AotDaslibList) -# ADD_AOT_EXT_FILE(AOT_GENERATED_SRC dasAotStub ${inF}) # No need to do aot, since these files already aot'ed -#ENDFOREACH() - -set(StandaloneFilesList - # # aot das-mode temporary disabled - # src/das/ast/ast_print.das - # src/das/ast/ast_aot_cpp.das - # src/das/ast/aot_constants.das - # src/das/ast/standalone_contexts.das -) - -FOREACH(inF IN LISTS StandaloneFilesList) - ADD_STANDALONE_FILE(AOT_GENERATED_SRC ${inF}) -ENDFOREACH() - - -SOURCE_GROUP_FILES("aot stub" AOT_GENERATED_SRC) -#UNITIZE_BUILD("daslib" AOT_GENERATED_SRC) - - -include_directories(../include) -include_directories(${PROJECT_SOURCE_DIR}/../3rdparty/fmt/include) -ADD_LIBRARY(libDaScript ${PARSER_GENERATED_SRC} ${PARSER_SRC} ${VECMATH_SRC} ${AST_SRC} ${BUILTIN_SRC} - ${MISC_SRC} ${SIMULATE_SRC} ${SIMULATE_FUSION_SRC} ${TEST_SRC} ${MAIN_SRC} - ${DAGOR_NOISE_SRC} ${DAS_HASH_MAP_SRC} ${DAS_LIB_SRC} ${DASCRIPT_FMT_SRC} ${AOT_STUB_SRC} ${AOT_GENERATED_SRC}) -ADD_DEPENDENCIES(libDaScript need_and_resolve) -ADD_PROJECT_XXD_DEPENDS(libDaScript) -target_include_directories(libDaScript PUBLIC - ${DAS_SMMALLOC_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/uriparser/include -) -target_link_libraries(libDaScript libUriParser) -IF(LINUX_UUID) -target_link_libraries(libDaScript uuid) -ENDIF() -IF(UNIX AND NOT APPLE) - TARGET_LINK_LIBRARIES(libDaScript ${CMAKE_DL_LIBS}) -ENDIF() -SETUP_CPP11(libDaScript) -#target_precompile_headers(libDaScript PUBLIC ../include/daScript/misc/platform.h) - - -if (NOT ${DAS_BUILD_TOOLS} MATCHES NO) -# Stand alone command line compiler - - SET(DASCRIPT_MAIN_SRC - ../utils/daScript/main.cpp - ${DAS_MODULES_NEED_INC} - ) - SOURCE_GROUP_FILES("main" DASCRIPT_MAIN_SRC) - - add_executable(daScript ${DASCRIPT_MAIN_SRC}) - TARGET_LINK_LIBRARIES(daScript libDaScript Threads::Threads ${DAS_MODULES_LIBS}) - SETUP_CPP11(daScript) - SETUP_LTO(daScript) - - -endif() diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000000..e2b0253eb2 --- /dev/null +++ b/web/README.md @@ -0,0 +1,83 @@ +# daslang — WebAssembly build + +Builds daslang (compiler + runtime) as WASM via Emscripten. +Outputs land in `output/`: `daslang.wasm`, `daslang_static.wasm`, `das-fmt.wasm`, plus JS loaders. + +## Prerequisites + +- **CMake ≥ 3.16**, **Ninja** +- **Emscripten SDK** — installed by the step scripts below (≈ 1 GB, clones into `web/emsdk/`) + +## Install emsdk (once) + +```bash +cd web/ +bash step0_emsdk_install.sh +``` + +Clones https://github.com/emscripten-core/emsdk into `web/emsdk/`. + +## Activate emsdk (each shell session) + +**Linux / macOS** +```bash +bash step1_emsdk_activate_linux.sh +``` + +**Windows** +```bat +step1_emsdk_activate_windows.sh +``` + +Installs the latest toolchain and puts `emcc` on `PATH`. Must be run in every new shell before building. + +## Configure and build + +**Release** (recommended, smaller `.wasm`): +```bash +cmake -DCMAKE_BUILD_TYPE=Release -G Ninja -DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake ../ +ninja +``` + +**Debug** (larger, with DWARF symbols): +```bash +cp ../CMakeXxdImpl.txt . +rm -rf cmake_temp && mkdir cmake_temp && cd cmake_temp +cmake -DCMAKE_BUILD_TYPE=Debug -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \ + ../ +ninja +``` + +First build takes 5–15 min; incremental rebuilds are faster (just re-run `ninja` from `cmake_temp/`). + +## Running locally + +WASM files require a local HTTP server (browsers block `file://` for WASM). + +```bash +cd output/ +python3 -m http.server 8080 +``` + +Open http://localhost:8080 — the in-browser daslang IDE loads. + +Or serve with any static server (Node `serve`, nginx, etc.). + +## Outputs + +| File | Description | +|---|---| +| `output/daslang_static.js` + `.wasm` | interpreter (no dynamic modules) | +| `output/das-fmt.js` + `.wasm` | Source formatter | +| `output/index.html` | Web IDE entry point | + +## Troubleshooting + +**`emcc: not found`** — run Step 1 (`source emsdk/emsdk_env.sh`) in the current shell. + +**CMake can't find Emscripten** — verify `EMSCRIPTEN` env var is set: `echo $EMSCRIPTEN`. + +**Build fails with SIGSEGV / stack overflow** — use Release build or increase stack size; Debug stack is much larger. + +**Browser shows blank page** — must serve via HTTP, not `file://`. diff --git a/web/step23_build_debug.sh b/web/step23_build_debug.sh deleted file mode 100644 index e0a5471d05..0000000000 --- a/web/step23_build_debug.sh +++ /dev/null @@ -1,2 +0,0 @@ -./step2_generate_ninja_debug.sh -./step3_ninja_build.sh \ No newline at end of file diff --git a/web/step23_build_release.sh b/web/step23_build_release.sh deleted file mode 100644 index 7f7f3681b4..0000000000 --- a/web/step23_build_release.sh +++ /dev/null @@ -1,2 +0,0 @@ -./step2_generate_ninja_release.sh -./step3_ninja_build.sh \ No newline at end of file diff --git a/web/step2_generate_ninja_debug.sh b/web/step2_generate_ninja_debug.sh deleted file mode 100644 index 772ba48816..0000000000 --- a/web/step2_generate_ninja_debug.sh +++ /dev/null @@ -1,7 +0,0 @@ -cp ../CMakeXxdImpl.txt . - -rm -r -f cmake_temp -mkdir cmake_temp -cd cmake_temp - -cmake -DCMAKE_BUILD_TYPE=Debug -G Ninja -DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake ../ diff --git a/web/step2_generate_ninja_release.sh b/web/step2_generate_ninja_release.sh deleted file mode 100644 index b8e39a319a..0000000000 --- a/web/step2_generate_ninja_release.sh +++ /dev/null @@ -1,7 +0,0 @@ -cp ../CMakeXxdImpl.txt . - -rm -r -f cmake_temp -mkdir cmake_temp -cd cmake_temp - -cmake -DCMAKE_BUILD_TYPE=Release -G Ninja -DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake ../ diff --git a/web/step3_ninja_build.sh b/web/step3_ninja_build.sh deleted file mode 100644 index 14cee84440..0000000000 --- a/web/step3_ninja_build.sh +++ /dev/null @@ -1,2 +0,0 @@ -cd cmake_temp -ninja \ No newline at end of file diff --git a/web/test/dastest_wasm.js b/web/test/dastest_wasm.js new file mode 100644 index 0000000000..3224e469d2 --- /dev/null +++ b/web/test/dastest_wasm.js @@ -0,0 +1,49 @@ +// Run dastest inside the WASM build by mounting the host repo via NODEFS. +// Usage: node dastest_wasm.js +// repo-root — path to daslang repo root (contains dastest/, tests/, daslib/) +// output-dir — path containing daslang_static.js + daslang.wasm +const path = require('path'); + +const repoRoot = path.resolve(process.argv[2]); +const outputDir = path.resolve(process.argv[3]); + +if (!repoRoot || !outputDir) { + console.error('usage: node dastest_wasm.js '); + process.exit(1); +} + +// Emscripten implements exit() by throwing ExitStatus, which propagates as an +// unhandled promise rejection. Intercept it to forward the exit code correctly. +process.on('unhandledRejection', (reason) => { + if (reason && reason.name === 'ExitStatus') { + process.exit(reason.status); + } + console.error('WASM error:', reason); + process.exit(1); +}); + +const Module = require(outputDir + '/daslang_static.js'); +Module.onRuntimeInitialized = function() { + Module.FS.mkdir('/repo'); + Module.FS.mount(Module.FS.filesystems.NODEFS, { root: repoRoot }, '/repo'); + + // daslib is already embedded at /daslib by --embed-file at build time. + // div_by_zero and try_recover use fatal C++ exceptions that escape the WASM + // callMain boundary before daslang's setjmp-based TryCatch can intercept them. + Module.callMain([ + '/repo/dastest/dastest.das', '--', + '--color', '--timeout=0', + // Excluded a bunch of tests with try/catch + '--exclude', 'div_by_zero', + '--exclude', 'try_recover', + '--exclude', 'func_addr', + '--exclude', 'move_and_return_move', + '--exclude', 'resize_locked', + '--exclude', 'strict_smart_ptr', + '--exclude', 'string_ops', + '--exclude', 'table_operations', + '--exclude', 'variant', + '--exclude', 'vec_index', + '--test', '/repo/tests/language' + ]); +}; diff --git a/web/output/index.html b/web/ui/index.html similarity index 100% rename from web/output/index.html rename to web/ui/index.html diff --git a/web/output/samples/data.json b/web/ui/samples/data.json similarity index 100% rename from web/output/samples/data.json rename to web/ui/samples/data.json diff --git a/web/output/samples/examples/func.das b/web/ui/samples/examples/func.das similarity index 100% rename from web/output/samples/examples/func.das rename to web/ui/samples/examples/func.das diff --git a/web/output/samples/examples/hello.das b/web/ui/samples/examples/hello.das similarity index 100% rename from web/output/samples/examples/hello.das rename to web/ui/samples/examples/hello.das diff --git a/web/output/samples/examples/loop.das b/web/ui/samples/examples/loop.das similarity index 100% rename from web/output/samples/examples/loop.das rename to web/ui/samples/examples/loop.das diff --git a/web/output/samples/tests/math.das b/web/ui/samples/tests/math.das similarity index 100% rename from web/output/samples/tests/math.das rename to web/ui/samples/tests/math.das diff --git a/web/output/samples/tests/math_simple.das b/web/ui/samples/tests/math_simple.das similarity index 100% rename from web/output/samples/tests/math_simple.das rename to web/ui/samples/tests/math_simple.das diff --git a/web/output/samples/tests/print.das b/web/ui/samples/tests/print.das similarity index 100% rename from web/output/samples/tests/print.das rename to web/ui/samples/tests/print.das diff --git a/web/output/samples/tests/random_sequence.das b/web/ui/samples/tests/random_sequence.das similarity index 100% rename from web/output/samples/tests/random_sequence.das rename to web/ui/samples/tests/random_sequence.das diff --git a/web/output/src/codemirror.css b/web/ui/src/codemirror.css similarity index 100% rename from web/output/src/codemirror.css rename to web/ui/src/codemirror.css diff --git a/web/output/src/codemirror.min.js b/web/ui/src/codemirror.min.js similarity index 100% rename from web/output/src/codemirror.min.js rename to web/ui/src/codemirror.min.js diff --git a/web/output/src/eclipse.css b/web/ui/src/eclipse.css similarity index 100% rename from web/output/src/eclipse.css rename to web/ui/src/eclipse.css diff --git a/web/output/src/jquery-3.6.0.min.js b/web/ui/src/jquery-3.6.0.min.js similarity index 100% rename from web/output/src/jquery-3.6.0.min.js rename to web/ui/src/jquery-3.6.0.min.js diff --git a/web/output/src/main.css b/web/ui/src/main.css similarity index 100% rename from web/output/src/main.css rename to web/ui/src/main.css diff --git a/web/output/src/main.js b/web/ui/src/main.js similarity index 99% rename from web/output/src/main.js rename to web/ui/src/main.js index 0c36e259fb..4ff702d419 100644 --- a/web/output/src/main.js +++ b/web/ui/src/main.js @@ -10,7 +10,7 @@ var sampleList = {"examples":null, "tests":null}; pageInit = function () { - $.getScript("daScript.js") + $.getScript("daslang_static.js") editorCode = document.getElementById("code");