-
Notifications
You must be signed in to change notification settings - Fork 1
Standard Library Architecture
Roger Johansson edited this page Jan 14, 2026
·
1 revision
How JavaScript built-in objects (Array, Promise, Map, etc.) are implemented in Asynkron.JsEngine.
flowchart TB
subgraph Attributes["Attribute-Driven"]
JsProto["[JsPrototype]"]
JsCtor["[JsConstructor]"]
JsMethod["[JsHostMethod]"]
end
subgraph Generator["Source Generator"]
Gen["PrototypeGenerator"]
end
subgraph Output["Generated Code"]
Proto["Prototype class"]
Ctor["Constructor class"]
Methods["Method bindings"]
end
subgraph Runtime["Runtime"]
Realm["RealmState"]
Global["Global Object"]
end
Attributes --> Generator
Generator --> Output
Output --> Runtime
The standard library uses attribute-based code generation to minimize boilerplate while ensuring ECMAScript compliance.
StdLib/
├── Array/
│ ├── ArrayConstructor.cs # Array constructor + static methods
│ ├── ArrayPrototype.cs # Array.prototype methods
│ └── ArrayPrototype.Methods.cs # Method implementations
├── Promise/
│ ├── PromiseConstructor.cs
│ └── PromisePrototype.cs
├── Error/
│ ├── ErrorConstructor.cs
│ ├── TypeErrorConstructor.cs
│ └── ...
├── Intl/ # Internationalization API
├── Temporal/ # Temporal API
└── StandardLibrary.cs # Error helpers, initialization
Marks a class as a JavaScript prototype object:
[JsPrototype("Array", ToStringTag = "Array", ObjectKind = PrototypeObjectKind.Array)]
[JsSymbolAlias("iterator", "values")] // [Symbol.iterator] = values
public sealed partial class ArrayPrototype
{
protected override void ConfigurePrototype()
{
Realm.ArrayPrototype ??= Prototype;
Prototype.DefineProperty("length",
new PropertyDescriptor { Value = 0d, Writable = true, Enumerable = false, Configurable = false });
}
}Attribute Properties:
| Property | Purpose |
|---|---|
IntrinsicName |
Diagnostic name (e.g., "Array") |
ToStringTag |
Value for [Symbol.toStringTag]
|
ObjectKind |
Underlying storage (Object, Array, etc.) |
InstanceType |
Type to extract from thisValue
|
TryGetMethod |
Custom extraction method |
[JsHostMethod("push", Length = 1d)]
public static JsValue Push(JsValue thisValue, IReadOnlyList<JsValue> args, RealmState? realm)
{
// Implementation
}
[JsHostMethod("map", Length = 1d)]
public static JsValue Map(JsValue thisValue, IReadOnlyList<JsValue> args, RealmState? realm)
{
// Implementation
}Method Signatures:
// Instance method (receives thisValue)
JsValue Method(JsValue thisValue, IReadOnlyList<JsValue> args, RealmState? realm)
// Static method (no thisValue)
JsValue Method(IReadOnlyList<JsValue> args, RealmState? realm)Marks a class as a JavaScript constructor:
[JsConstructor("Array", PrototypeType = typeof(ArrayPrototype), Length = 1d, DisplayName = "Array")]
public sealed partial class ArrayConstructor : JsConstructor
{
[JsConstructorMethod("isArray", Length = 1d)]
public static JsValue IsArray(IReadOnlyList<JsValue> args, RealmState? realm)
{
return new JsValue(ArrayIsArray(args.GetArgument(0), realm));
}
[JsConstructorSymbolGetter("species")]
public static JsValue GetSpecies(JsValue thisValue)
{
return thisValue; // Array[Symbol.species] returns Array
}
protected override JsValue ConstructInstance(JsValue thisValue, IReadOnlyList<JsValue> args)
{
var array = AllocateArrayInstance(thisValue);
InitializeArrayLength(array, args);
return JsValue.FromJsArray(array);
}
}Attribute Properties:
| Property | Purpose |
|---|---|
IntrinsicName |
Constructor name |
PrototypeType |
Associated prototype class |
Length |
Function.length property |
DisplayName |
Function.name property |
The source generator produces:
// ArrayPrototype.g.cs (generated)
public sealed partial class ArrayPrototype : JsPrototype
{
private void RegisterMethods()
{
DefineMethod("push", Push, length: 1d);
DefineMethod("pop", Pop, length: 0d);
DefineMethod("map", Map, length: 1d);
// ... 30+ methods
DefineSymbolMethod(SymbolKeys.Iterator, Values); // from [JsSymbolAlias]
}
internal static JsValue RequireInstance(JsValue thisValue, RealmState realm)
{
if (!thisValue.TryGetObject<JsArray>(out var array))
throw StandardLibrary.ThrowTypeError("Array method called on non-array");
return array;
}
}flowchart TB
Error((Error))
Error --> TypeError((TypeError))
Error --> RangeError((RangeError))
Error --> ReferenceError((ReferenceError))
Error --> SyntaxError((SyntaxError))
Error --> URIError((URIError))
Error --> EvalError((EvalError))
Error --> AggregateError((AggregateError))
// In StandardLibrary.cs
internal static JsValue CreateTypeError(string message, EvaluationContext? context = null, RealmState? realm = null)
{
realm ??= context?.RealmState;
if (realm?.TypeErrorConstructor is not IJsCallable callable)
{
return CreateErrorFallback("TypeError", message, realm);
}
return callable.Invoke(new SingleValueArgs(new JsValue(message)), JsValue.Null);
}
internal static ThrowSignal ThrowTypeError(string message, ...)
{
return new ThrowSignal(CreateTypeError(message, ...));
}[JsHostMethod("push")]
public static JsValue Push(JsValue thisValue, IReadOnlyList<JsValue> args, RealmState? realm)
{
if (!thisValue.TryGetObject<JsArray>(out var array))
throw StandardLibrary.ThrowTypeError("Array.prototype.push called on non-array", realm: realm);
// Implementation...
}Each engine instance has a RealmState that holds constructor references:
public class RealmState
{
// Constructors
public IJsCallable? ArrayConstructor { get; set; }
public IJsCallable? ObjectConstructor { get; set; }
public IJsCallable? FunctionConstructor { get; set; }
public IJsCallable? TypeErrorConstructor { get; set; }
public IJsCallable? PromiseConstructor { get; set; }
// ... 30+ constructors
// Prototypes
public IJsPropertyAccessor? ArrayPrototype { get; set; }
public IJsPropertyAccessor? ObjectPrototype { get; set; }
public IJsPropertyAccessor? FunctionPrototype { get; set; }
// ... 30+ prototypes
// Logger
public ILogger? Logger { get; }
// Engine reference
public JsEngine? Engine { get; }
}// StdLib/MyType/MyTypePrototype.cs
[JsPrototype("MyType", ToStringTag = "MyType", InstanceType = typeof(MyTypeInstance))]
public sealed partial class MyTypePrototype
{
protected override void ConfigurePrototype()
{
Realm.MyTypePrototype ??= Prototype;
}
[JsHostMethod("doSomething", Length = 1d)]
public static JsValue DoSomething(JsValue thisValue, IReadOnlyList<JsValue> args, RealmState? realm)
{
var instance = RequireInstance(thisValue, realm);
// Implementation...
return JsValue.Undefined;
}
}// StdLib/MyType/MyTypeConstructor.cs
[JsConstructor("MyType", PrototypeType = typeof(MyTypePrototype), Length = 0d)]
public sealed partial class MyTypeConstructor : JsConstructor
{
protected override JsValue ConstructInstance(JsValue thisValue, IReadOnlyList<JsValue> args)
{
var instance = new MyTypeInstance(Realm);
instance.SetPrototype(Prototype);
return (JsValue)instance;
}
protected override void ConfigureConstructor(HostFunction constructor)
{
Realm.MyTypeConstructor ??= constructor;
}
}// In engine initialization
var myTypeCtor = new MyTypeConstructor(objectPrototype, realm);
myTypeCtor.Initialize();
globalObject.DefineProperty("MyType", new PropertyDescriptor { Value = myTypeCtor.Constructor });[JsPrototype("Array")]
[JsSymbolAlias("iterator", "values")] // [Symbol.iterator] points to values method
public sealed partial class ArrayPrototype { }[JsConstructorSymbolGetter("species")]
public static JsValue GetSpecies(JsValue thisValue)
{
return thisValue; // Return the constructor itself
}// Common symbol keys
SymbolKeys.Iterator // Symbol.iterator
SymbolKeys.AsyncIterator // Symbol.asyncIterator
SymbolKeys.ToStringTag // Symbol.toStringTag
SymbolKeys.ToPrimitive // Symbol.toPrimitive
SymbolKeys.Species // Symbol.species
SymbolKeys.HasInstance // Symbol.hasInstance
SymbolKeys.Unscopables // Symbol.unscopables// Data descriptor
Prototype.DefineProperty("length",
new PropertyDescriptor
{
Value = 0d,
Writable = true,
Enumerable = false,
Configurable = false
});
// Accessor descriptor
Prototype.DefineProperty("size",
new PropertyDescriptor
{
Get = sizeGetter,
Set = null,
Enumerable = false,
Configurable = true
});[JsHostMethod("map", Writable = true, Enumerable = false, Configurable = true)]Default values match ECMAScript spec:
Writable = trueEnumerable = falseConfigurable = true
- JsValue System - Value representation
- JsObject & Properties - Property descriptor details
- Host Integration API - Exposing custom functions
- Promise & Microtasks - Promise implementation