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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion .claude/scheduled_tasks.lock

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import { EventEmitter, EventEmitterOfT, type IEventEmitter, type IEventEmitterOf
import { Logger } from '@coderline/alphatab/Logger';
import type { ISynthOutput, ISynthOutputDevice } from '@coderline/alphatab/synth/ISynthOutput';

/**
* @target web
* @internal
*/
declare const webkitAudioContext: any;

/**
* @target
* @target web
* @internal
*/
export class AlphaSynthWebAudioSynthOutputDevice implements ISynthOutputDevice {
Expand Down
3 changes: 2 additions & 1 deletion packages/csharp/src/AlphaTab.Test/TestPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ static partial class TestPlatform
var currentDir = new DirectoryInfo(System.Environment.CurrentDirectory);
while (currentDir != null)
{
if (currentDir.GetDirectories(".git").Length == 1)
var dotGit = Path.Combine(currentDir.FullName, ".git");
if (Directory.Exists(dotGit) || File.Exists(dotGit))
{
return Path.Join(currentDir.FullName, "packages", "alphatab");
}
Expand Down
9 changes: 9 additions & 0 deletions packages/csharp/src/AlphaTab/Collections/Map.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ public Map(IEnumerable<ArrayTuple<TKey, TValue>> entries)
this[entry.V0] = entry.V1;
}
}

public Map(params ArrayTuple<TKey, TValue>[] entries)
{
foreach (var entry in entries)
{
this[entry.V0] = entry.V1;
}
}

public Map(IEnumerable<KeyValuePair<TKey, TValue>> entries)
{
foreach (var entry in entries)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,11 @@ internal inline fun <reified T> List<T>.concat(other: Iterable<T>): List<T> {
copy.push(other)
return copy
}

internal inline fun Throwable.cause(): Throwable? {
return this.cause
}

internal inline fun Throwable.stack(): String {
return this.stackTraceToString()
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package alphaTab.core.ecmaScript

import alphaTab.core.ArrayTuple
import alphaTab.core.IArrayTuple

public class Record<TKey, TValue> : alphaTab.collections.Map<TKey, TValue> {
constructor() : super()
constructor(vararg elements: ArrayTuple<TKey, TValue>) : super(elements.asIterable())
constructor(vararg elements: IArrayTuple<TKey, TValue>) : super(elements.asIterable())
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,7 @@ class NotExpector<T>(private val actual: T, private val message: String? = null)
}

class Expector<T>(private val actual: T, private val message: String? = null) {
val not
get() = NotExpector(actual, message)
fun not() = NotExpector(actual, message)

fun equal(expected: Any?, message: String? = null) {
var actualToCheck = actual
Expand Down
29 changes: 29 additions & 0 deletions packages/transpiler/biome.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.5/schema.json",
"root": false,
"extends": "//",
"files": {
"includes": [
"*.ts",
"src/**",
"test/**",
"!test/fixtures/**"
]
},
"formatter": {
"includes": [
"*.ts",
"src/**",
"test/**",
"!test/fixtures/**"
]
},
"linter": {
"includes": [
"*.ts",
"src/**",
"test/**",
"!test/fixtures/**"
]
}
}
68 changes: 68 additions & 0 deletions packages/transpiler/docs/IR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Transpiler IR

The intermediate representation (IR) is the data structure the transpiler operates on between parsing TypeScript and emitting C# or Kotlin source.

The canonical definitions live in [`Ir.ts`](Ir.ts). Both the C# and Kotlin targets consume the same IR; per-target differences manifest only in:

- the printer (`CSharpAstPrinter` / `KotlinAstPrinter`),
- the per-target strategy hooks declared by [`../src/csharp/TargetStrategy.ts`](../csharp/TargetStrategy.ts).

Import the IR via `import * as cs from '../src/ir/Ir'`. The `cs` alias is historical (the C# target was implemented first); the namespace is the canonical IR shared by all targets.

## Pipeline

The IR moves through three named stages in every emit:

```
TypeScript source
1. AstTransformer ── per-file walk, produces a raw IR SourceFile per TS root
2. PassPipeline ── named whole-program passes mutate the IR in place
│ (resolve-types, rewrite-visibilities, ...)
3. AstPrinter ── per-file walk, emits .cs/.kt text
```

The transformer is the only stage allowed to allocate new `tsSymbol`-backed nodes from TypeScript. Passes mutate existing nodes (set flags, replace expressions, propagate up the inheritance graph). The printer is read-only over the IR.

## Invariants

The following invariants must hold whenever the IR enters the printer stage:

1. **No `UnresolvedTypeNode`.** Every `TypeNode` reachable from any `SourceFile` must be a concrete kind (`PrimitiveTypeNode`, `ArrayTypeNode`, `MapTypeNode`, `ArrayTupleNode`, `FunctionTypeNode`, `TypeReference`, or a `NamedTypeDeclaration`). The `resolve-types` pass enforces this; retiring `UnresolvedTypeNode` entirely is a planned follow-up.
2. **Every node has a `parent`.** With one documented exception: see "Paren wrapping" below.
3. **Override propagation has run.** After `rewrite-visibilities`, every method or property that overrides a virtual base must have either `isOverride: true` (set by the transformer) or, after the pass, `isVirtual: true` if it is itself an override target. The pass also sets `hasVirtualMembersOrSubClasses` on enclosing types.
4. **Naming conventions applied.** All identifier strings on member-access nodes have already been routed through the target's `toMethodNameCase` / `toPropertyNameCase`; the printer does not re-case.
5. **Smart-cast lowering applied.** The transformer wraps any expression that requires a runtime type narrowing through `SmartCastResolver`. Printers do no type inference of their own.

## Syntax and runtime support reference

A full catalogue of supported constructs, partial support, gaps, and runtime built-in mappings lives in [`SYNTAX.md`](SYNTAX.md). [`LIMITATIONS.md`](LIMITATIONS.md) redirects there.

## Paren wrapping exception

`paren(parent, inner, tsNode)` in [`../csharp/CSharpAstBuilder.ts`](../csharp/CSharpAstBuilder.ts) does **not** rewire `inner.parent`. Several transformer paths (smart-cast walk-up, the `_coerceIntegerBitOp` `nextParent` chain) rely on the inner expression's parent still pointing at its original context to make routing decisions. Code that wraps an expression in parens and then expects to walk up the tree from the inner expression must be aware of this.

## Per-target extension points

The [`TargetStrategy`](../csharp/TargetStrategy.ts) interface enumerates every place the IR's emitted shape can differ between targets:

- naming conventions (4 cases),
- core type name rewriting (`toCoreTypeName`),
- runtime type aliases (`make{Exception,Iterable,Iterator,Generator}Type`),
- module / namespace mapping (`getDefaultUsings`),
- symbol name rewrites (`getNameFromSymbol`, `getClassName`),
- inheritance lookup (`getOverriddenMembers`),
- identifier / module tag (`targetTag`, `alphaSkiaModule`).

Both `CSharpEmitterContext` and `KotlinEmitterContext` implement this interface today (inheritance-based); a planned follow-up converts the relationship to composition (`context` accepts a `TargetStrategy` field).

## Adding a new pass

1. Create a class implementing [`IrPass`](../src/passes/IrPass.ts) under `../src/passes/`.
2. Add it to the pass list of the relevant emitter (`CSharpEmitter.ts` or `KotlinEmitter.ts`).
3. Add a fixture under `test/fixtures/` that exercises the pass's behaviour.
4. Run `npm test` to confirm snapshots still match (byte-identical output is the default; if the pass intentionally changes output, regenerate snapshots with `UPDATE_SNAPSHOTS=1` and document the change).
Loading
Loading