json-as
Version:
The only JSON library you'll need for AssemblyScript. SIMD enabled
301 lines (276 loc) • 9.94 kB
text/typescript
import { ClassDeclaration, Expression, FieldDeclaration, Source, NodeKind, Node, NamespaceDeclaration, DeclarationStatement, TypeName, Parser, ImportStatement, CommonFlags, EnumDeclaration } from "assemblyscript/dist/assemblyscript.js";
import { TypeAlias } from "./linkers/alias.js";
import { stripNull } from "./index.js";
export enum PropertyFlags {
OmitNull,
OmitIf,
Raw,
Custom,
}
export class Property {
public name: string = "";
public alias: string | null = null;
public type: string = "";
public value: string | null = null;
public flags: Map<PropertyFlags, Expression | null> = new Map<PropertyFlags, Expression | null>();
public node!: FieldDeclaration;
public byteSize: number = 0;
public _generic: boolean = false;
public _custom: boolean = false;
public parent: Schema;
set custom(value: boolean) {
this._custom = value;
}
get custom(): boolean {
if (this._custom) return true;
if (this.parent.node.isGeneric && this.parent.node.typeParameters.some((p) => p.name.text == this.type)) {
// console.log("Custom (Generic): " + this.name);
// this._generic = true;
this._custom = true;
return true;
}
for (const dep of this.parent.deps) {
if (this.name == dep.name && dep.custom) {
// console.log("Custom (Dependency): " + this.name);
this._custom = true;
return true;
}
}
return false;
}
set generic(value: boolean) {
this._generic = value;
}
get generic(): boolean {
if (this._generic) return true;
if (this.parent.node.isGeneric && this.parent.node.typeParameters.some((p) => p.name.text == stripNull(this.type))) {
// console.log("Generic: " + this.name);
this._generic = true;
return true;
}
return false;
}
}
export class Schema {
public static: boolean = true;
public name: string = "";
public members: Property[] = [];
public parent: Schema | null = null;
public node!: ClassDeclaration;
public needsLink: string | null = null;
public byteSize: number = 0;
public deps: Schema[] = [];
private _custom: boolean = false;
set custom(value: boolean) {
this._custom = value;
}
get custom(): boolean {
if (this._custom) return true;
if (this.parent) return this.parent.custom;
}
}
export class SourceSet {
private sources: Record<string, Src> = {};
/**
* Get a stored source from the set, or create a new one and store it if it
* didn't exist.
* @param source AssemblyScript Source
* @returns Source object
*/
get(source: Source): Src {
let src = this.sources[source.internalPath];
if (!src) {
src = new Src(source, this);
this.sources[source.internalPath] = src;
}
return src;
}
}
export class Src {
public internalPath: string;
public normalizedPath: string;
public schemas: Schema[];
public aliases: TypeAlias[];
public exports: Schema[];
private nodeMap: Map<Node, NamespaceDeclaration[]> = new Map<Node, NamespaceDeclaration[]>();
private classes: Record<string, ClassDeclaration> = {};
private enums: Record<string, EnumDeclaration> = {};
private imports: ImportStatement[] = [];
constructor(
source: Source,
private sourceSet: SourceSet,
) {
this.internalPath = source.internalPath;
this.normalizedPath = source.normalizedPath;
this.aliases = TypeAlias.getAliases(source);
this.traverse(source.statements, []);
}
/**
* Traverse source nodes and finds all classes and imports, and which namespaces they exist under.
* @param nodes Nodes to traverse.
* @param path The current path of namespace declarations leading to the nodes.
*/
private traverse(nodes: Node[], path: NamespaceDeclaration[]) {
for (let node of nodes) {
switch (node.kind) {
case NodeKind.NamespaceDeclaration:
const namespaceDeclaration = node as NamespaceDeclaration;
this.traverse(namespaceDeclaration.members, [...path, namespaceDeclaration]);
break;
case NodeKind.ClassDeclaration:
const classDeclaration = node as ClassDeclaration;
this.classes[this.qualifiedName(classDeclaration, path)] = classDeclaration;
break;
case NodeKind.EnumDeclaration:
const enumDeclaration = node as EnumDeclaration;
this.enums[this.qualifiedName(enumDeclaration, path)] = enumDeclaration;
break;
case NodeKind.Import:
const importStatement = node as ImportStatement;
this.imports.push(importStatement);
break;
}
this.nodeMap.set(node, path);
}
}
/**
* Get the qualified name (eg. "Namespace.BaseObject") for a class.
* @param node Class declaration.
* @returns Qualified name
*/
getQualifiedName(node: DeclarationStatement): string {
return this.qualifiedName(node, this.nodeMap.get(node));
}
/**
* Get a class declaration by its qualified name.
* @param qualifiedName Qualified named (eg. "Namespace.BaseObject")
* @returns Class declaration or null if not found.
*/
getClass(qualifiedName: string): ClassDeclaration | null {
return this.classes[qualifiedName] || null;
}
/**
* Get an enum declaration by its qualified name.
* @param qualifiedName Qualified name (eg. "Namespace.MyEnum")
* @returns Enum declaration or null if not found.
*/
getEnum(qualifiedName: string): EnumDeclaration | null {
return this.enums[qualifiedName] || null;
}
/**
* Get imported class from other sources in the parser.
* @param qualifiedName Qualified name of class.
* @param parser AssemblyScript parser.
* @returns Class declaration or null if not found.
*/
getImportedClass(qualifiedName: string, parser: Parser): ClassDeclaration | null {
for (const stmt of this.imports) {
const externalSource = parser.sources.filter((src) => src.internalPath != this.internalPath).find((src) => src.internalPath == stmt.internalPath);
if (!externalSource) continue;
const source = this.sourceSet.get(externalSource);
const classDeclaration = source.getClass(qualifiedName);
if (classDeclaration && classDeclaration.flags & CommonFlags.Export) {
return classDeclaration;
}
}
return null;
}
/**
* Get imported enum from other sources in the parser.
* @param qualifiedName Qualified name of enum.
* @param parser AssemblyScript parser.
* @returns Enum declaration or null if not found.
*/
getImportedEnum(qualifiedName: string, parser: Parser): EnumDeclaration | null {
for (const stmt of this.imports) {
const externalSource = parser.sources.filter((src) => src.internalPath != this.internalPath).find((src) => src.internalPath == stmt.internalPath);
if (!externalSource) continue;
const source = this.sourceSet.get(externalSource);
const enumDeclaration = source.getEnum(qualifiedName);
if (enumDeclaration && enumDeclaration.flags & CommonFlags.Export) {
return enumDeclaration;
}
}
return null;
}
/**
* Gets a unique path string to the node by combining the internalPath with
* the qualified name of the node.
* @param node DeclarationStatement
*/
getFullPath(node: DeclarationStatement): string {
return this.internalPath + "/" + this.getQualifiedName(node);
}
/**
* Resolved the qualified name of the extended class for a class
* declaration.
* @param classDeclaration Class declaration that extends another class.
* @returns Qualified name of the extended class, or empty string if not extending any class.
*/
resolveExtendsName(classDeclaration: ClassDeclaration): string {
const parents = this.nodeMap.get(classDeclaration);
if (!classDeclaration.extendsType || !parents) {
return "";
}
const name = classDeclaration.extendsType.name.identifier.text;
const extendsName = this.getIdentifier(classDeclaration.extendsType.name);
// Reverse walk to find first class or namespace that matches the first part
// of type name.
for (let i = parents.length - 1; i >= 0; i--) {
const parent = parents[i];
for (let node of parent.members) {
if (name == this.getNamespaceOrClassName(node)) {
// Add namespace path to the extendsName.
return (
parents
.slice(0, i + 1)
.map((p) => p.name.text)
.join(".") +
"." +
extendsName
);
}
}
}
// No matching class or namespace found. Just use the extendsName.
return extendsName;
}
/**
* Get the qualified name (eg "Namespace.BaseObject") of a class.
* @param node Class declaration.
* @param parents Array of namespace parents.
* @returns Qualified name
*/
private qualifiedName(node: DeclarationStatement, parents: NamespaceDeclaration[]): string {
return parents?.length ? parents.map((p) => p.name.text).join(".") + "." + node.name.text : node.name.text;
}
/**
* Checks if the node is either a namespace or class, and returns the simple
* name of the node.
* @param node Node to check
* @returns Name of namespace or class, or empty string if other type of node.
*/
private getNamespaceOrClassName(node: Node): string {
switch (node.kind) {
case NodeKind.NamespaceDeclaration:
return (node as DeclarationStatement).name.text;
case NodeKind.ClassDeclaration:
return (node as DeclarationStatement).name.text;
}
return "";
}
/**
* Get the full name (eg. "Namespace.Base") of a type name such as an
* extendedType.
* @param typeName Type name
* @returns Full name
*/
private getIdentifier(typeName: TypeName): string {
let names = [];
while (typeName) {
names.push(typeName.identifier.text);
typeName = typeName.next;
}
return names.join(".");
}
}