mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-14 16:35:03 +01:00
start structs
This commit is contained in:
parent
f6f6673721
commit
b52abed441
8 changed files with 214 additions and 62 deletions
3
README.md
Normal file
3
README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# a random language compiling to wasm
|
||||||
|
|
||||||
|
The language is not well designed. It's just random garbage.
|
||||||
53
src/ast.ts
53
src/ast.ts
|
|
@ -10,10 +10,15 @@ export type Identifier = {
|
||||||
|
|
||||||
export type ItemId = number;
|
export type ItemId = number;
|
||||||
|
|
||||||
export type ItemKind = {
|
export type ItemKind =
|
||||||
kind: "function";
|
| {
|
||||||
node: FunctionDef;
|
kind: "function";
|
||||||
};
|
node: FunctionDef;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
kind: "type";
|
||||||
|
node: TypeDef;
|
||||||
|
};
|
||||||
|
|
||||||
export type Item = ItemKind & {
|
export type Item = ItemKind & {
|
||||||
span: Span;
|
span: Span;
|
||||||
|
|
@ -34,6 +39,17 @@ export type FunctionArg = {
|
||||||
span: Span;
|
span: Span;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TypeDef = {
|
||||||
|
name: string;
|
||||||
|
fields: FieldDef[];
|
||||||
|
ty?: TyStruct;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FieldDef = {
|
||||||
|
name: Identifier;
|
||||||
|
type: Type;
|
||||||
|
};
|
||||||
|
|
||||||
export type ExprEmpty = { kind: "empty" };
|
export type ExprEmpty = { kind: "empty" };
|
||||||
|
|
||||||
export type ExprLet = {
|
export type ExprLet = {
|
||||||
|
|
@ -262,7 +278,21 @@ export type TyVar = {
|
||||||
index: number;
|
index: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Ty = TyString | TyInt | TyBool | TyList | TyTuple | TyFn | TyVar;
|
export type TyStruct = {
|
||||||
|
kind: "struct";
|
||||||
|
name: string;
|
||||||
|
fields: [string, Ty][];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Ty =
|
||||||
|
| TyString
|
||||||
|
| TyInt
|
||||||
|
| TyBool
|
||||||
|
| TyList
|
||||||
|
| TyTuple
|
||||||
|
| TyFn
|
||||||
|
| TyVar
|
||||||
|
| TyStruct;
|
||||||
|
|
||||||
export function tyIsUnit(ty: Ty): ty is TyUnit {
|
export function tyIsUnit(ty: Ty): ty is TyUnit {
|
||||||
return ty.kind === "tuple" && ty.elems.length === 0;
|
return ty.kind === "tuple" && ty.elems.length === 0;
|
||||||
|
|
@ -331,6 +361,19 @@ export function superFoldItem(item: Item, folder: Folder): Item {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "type": {
|
||||||
|
const fields = item.node.fields.map(({ name, type }) => ({
|
||||||
|
name,
|
||||||
|
type: folder.type(type),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "type",
|
||||||
|
span: item.span,
|
||||||
|
node: { name: item.node.name, fields },
|
||||||
|
id: item.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
10
src/index.ts
10
src/index.ts
|
|
@ -10,8 +10,11 @@ import fs from "fs";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
|
|
||||||
const input = `
|
const input = `
|
||||||
function main() = ();
|
type Uwu = (
|
||||||
function test(i: Int, j: Int): Bool = false == (i == 0);
|
meow: String,
|
||||||
|
);
|
||||||
|
|
||||||
|
function main(a: Int, b: Uwu) = ();
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
|
|
@ -37,9 +40,10 @@ function main() {
|
||||||
console.log(resolvedPrinted);
|
console.log(resolvedPrinted);
|
||||||
|
|
||||||
console.log("-----AST typecked------");
|
console.log("-----AST typecked------");
|
||||||
|
|
||||||
const typecked = typeck(resolved);
|
const typecked = typeck(resolved);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
console.log("-----wasm--------------");
|
console.log("-----wasm--------------");
|
||||||
const wasmModule = lowerToWasm(typecked);
|
const wasmModule = lowerToWasm(typecked);
|
||||||
const moduleStringColor = writeModuleWatToString(wasmModule, true);
|
const moduleStringColor = writeModuleWatToString(wasmModule, true);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export type DatalessToken =
|
||||||
| "if"
|
| "if"
|
||||||
| "then"
|
| "then"
|
||||||
| "else"
|
| "else"
|
||||||
|
| "type"
|
||||||
| "("
|
| "("
|
||||||
| ")"
|
| ")"
|
||||||
| "["
|
| "["
|
||||||
|
|
@ -209,6 +210,7 @@ const keywords = new Set<string>([
|
||||||
"if",
|
"if",
|
||||||
"then",
|
"then",
|
||||||
"else",
|
"else",
|
||||||
|
"type",
|
||||||
]);
|
]);
|
||||||
function isKeyword(kw: string): DatalessToken | undefined {
|
function isKeyword(kw: string): DatalessToken | undefined {
|
||||||
return keywords.has(kw) ? (kw as DatalessToken) : undefined;
|
return keywords.has(kw) ? (kw as DatalessToken) : undefined;
|
||||||
|
|
|
||||||
|
|
@ -303,6 +303,8 @@ function computeAbi(ty: TyFn): Abi {
|
||||||
return paramAbi(param.elems[0]);
|
return paramAbi(param.elems[0]);
|
||||||
}
|
}
|
||||||
todo("complex tuple abi");
|
todo("complex tuple abi");
|
||||||
|
case "struct":
|
||||||
|
todo("struct ABI");
|
||||||
case "var":
|
case "var":
|
||||||
varUnreachable();
|
varUnreachable();
|
||||||
}
|
}
|
||||||
|
|
@ -333,6 +335,8 @@ function computeAbi(ty: TyFn): Abi {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
todo("complex tuple abi");
|
todo("complex tuple abi");
|
||||||
|
case "struct":
|
||||||
|
todo("struct ABI");
|
||||||
case "var":
|
case "var":
|
||||||
varUnreachable();
|
varUnreachable();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
128
src/parser.ts
128
src/parser.ts
|
|
@ -5,16 +5,18 @@ import {
|
||||||
BinaryKind,
|
BinaryKind,
|
||||||
COMPARISON_KINDS,
|
COMPARISON_KINDS,
|
||||||
Expr,
|
Expr,
|
||||||
|
FieldDef,
|
||||||
FunctionArg,
|
FunctionArg,
|
||||||
FunctionDef,
|
FunctionDef,
|
||||||
Item,
|
Item,
|
||||||
LOGICAL_KINDS,
|
LOGICAL_KINDS,
|
||||||
Type,
|
Type,
|
||||||
|
TypeDef,
|
||||||
UNARY_KINDS,
|
UNARY_KINDS,
|
||||||
UnaryKind,
|
UnaryKind,
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { CompilerError, Span, todo } from "./error";
|
import { CompilerError, Span, todo } from "./error";
|
||||||
import { BaseToken, Token, TokenIdent } from "./lexer";
|
import { BaseToken, DatalessToken, Token, TokenIdent } from "./lexer";
|
||||||
|
|
||||||
type Parser<T> = (t: Token[]) => [Token[], T];
|
type Parser<T> = (t: Token[]) => [Token[], T];
|
||||||
|
|
||||||
|
|
@ -41,24 +43,16 @@ function parseItem(t: Token[]): [Token[], Item] {
|
||||||
|
|
||||||
[t] = expectNext(t, "(");
|
[t] = expectNext(t, "(");
|
||||||
|
|
||||||
const args: FunctionArg[] = [];
|
let args: FunctionArg[];
|
||||||
let first = true;
|
[t, args] = parseCommaSeparatedList(t, ")", (t) => {
|
||||||
while (next(t)[1]?.kind !== ")") {
|
|
||||||
if (!first) {
|
|
||||||
[t] = expectNext(t, ",");
|
|
||||||
}
|
|
||||||
first = false;
|
|
||||||
|
|
||||||
let name;
|
let name;
|
||||||
[t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier");
|
[t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier");
|
||||||
[t] = expectNext(t, ":");
|
[t] = expectNext(t, ":");
|
||||||
let type;
|
let type;
|
||||||
[t, type] = parseType(t);
|
[t, type] = parseType(t);
|
||||||
|
|
||||||
args.push({ name: name.ident, type, span: name.span });
|
return [t, { name: name.ident, type, span: name.span }];
|
||||||
}
|
});
|
||||||
|
|
||||||
[t] = expectNext(t, ")");
|
|
||||||
|
|
||||||
let colon;
|
let colon;
|
||||||
let returnType = undefined;
|
let returnType = undefined;
|
||||||
|
|
@ -91,6 +85,33 @@ function parseItem(t: Token[]): [Token[], Item] {
|
||||||
id: 0,
|
id: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
} else if (tok.kind === "type") {
|
||||||
|
let name;
|
||||||
|
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
||||||
|
[t] = expectNext(t, "=");
|
||||||
|
[t] = expectNext(t, "(");
|
||||||
|
|
||||||
|
let fields;
|
||||||
|
[t, fields] = parseCommaSeparatedList<FieldDef>(t, ")", (t) => {
|
||||||
|
let name;
|
||||||
|
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
||||||
|
[t] = expectNext(t, ":");
|
||||||
|
let type;
|
||||||
|
[t, type] = parseType(t);
|
||||||
|
return [t, { name: {
|
||||||
|
name: name.ident,
|
||||||
|
span: name.span,
|
||||||
|
}, type }];
|
||||||
|
});
|
||||||
|
|
||||||
|
[t] = expectNext(t, ";");
|
||||||
|
|
||||||
|
const def: TypeDef = {
|
||||||
|
name: name.ident,
|
||||||
|
fields,
|
||||||
|
};
|
||||||
|
|
||||||
|
return [t, { kind: "type", node: def, span: name.span, id: 0 }];
|
||||||
} else {
|
} else {
|
||||||
unexpectedToken(tok);
|
unexpectedToken(tok);
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +146,7 @@ function parseExpr(t: Token[]): [Token[], Expr] {
|
||||||
if (peak.kind === "let") {
|
if (peak.kind === "let") {
|
||||||
[t] = expectNext(t, "let");
|
[t] = expectNext(t, "let");
|
||||||
let name;
|
let name;
|
||||||
[t, name] = expectNext<TokenIdent & Token>(t, "identifier");
|
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
||||||
|
|
||||||
let type = undefined;
|
let type = undefined;
|
||||||
let colon;
|
let colon;
|
||||||
|
|
@ -236,15 +257,9 @@ function parseExprCall(t: Token[]): [Token[], Expr] {
|
||||||
while (next(t)[1].kind === "(") {
|
while (next(t)[1].kind === "(") {
|
||||||
let popen;
|
let popen;
|
||||||
[t, popen] = next(t);
|
[t, popen] = next(t);
|
||||||
const args = [];
|
|
||||||
while (next(t)[1].kind !== ")") {
|
let args;
|
||||||
let arg;
|
[t, args] = parseCommaSeparatedList(t, ")", parseExpr);
|
||||||
[t, arg] = parseExpr(t);
|
|
||||||
args.push(arg);
|
|
||||||
// TODO i think this is incorrect
|
|
||||||
[t] = eat(t, ",");
|
|
||||||
}
|
|
||||||
[t] = expectNext(t, ")");
|
|
||||||
|
|
||||||
lhs = { kind: "call", span: popen.span, lhs, args };
|
lhs = { kind: "call", span: popen.span, lhs, args };
|
||||||
}
|
}
|
||||||
|
|
@ -331,20 +346,23 @@ function parseType(t: Token[]): [Token[], Type] {
|
||||||
return [t, { kind: "list", elem, span }];
|
return [t, { kind: "list", elem, span }];
|
||||||
}
|
}
|
||||||
case "(": {
|
case "(": {
|
||||||
let first = true;
|
if (next(t)[1]?.kind === ")") {
|
||||||
const elems = [];
|
[t] = next(t);
|
||||||
while (next(t)[1]?.kind !== ")") {
|
return [t, { kind: "tuple", elems: [], span }];
|
||||||
if (!first) {
|
|
||||||
[t] = expectNext(t, ",");
|
|
||||||
}
|
|
||||||
first = false;
|
|
||||||
let type;
|
|
||||||
[t, type] = parseType(t);
|
|
||||||
elems.push(type);
|
|
||||||
}
|
}
|
||||||
[t] = expectNext(t, ")");
|
let head;
|
||||||
|
[t, head] = parseType(t);
|
||||||
|
|
||||||
return [t, { kind: "tuple", elems, span }];
|
if (next(t)[1]?.kind === ")") {
|
||||||
|
[t] = next(t);
|
||||||
|
// Just a type inside parens, not a tuple. `(T,)` is a tuple.
|
||||||
|
return [t, head];
|
||||||
|
}
|
||||||
|
|
||||||
|
let tail;
|
||||||
|
[t, tail] = parseCommaSeparatedList(t, ")", parseType);
|
||||||
|
|
||||||
|
return [t, { kind: "tuple", elems: [head, ...tail], span }];
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new CompilerError(
|
throw new CompilerError(
|
||||||
|
|
@ -357,6 +375,42 @@ function parseType(t: Token[]): [Token[], Type] {
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
|
function parseCommaSeparatedList<R>(
|
||||||
|
t: Token[],
|
||||||
|
terminator: Token["kind"],
|
||||||
|
parser: Parser<R>
|
||||||
|
): [Token[], R[]] {
|
||||||
|
const items: R[] = [];
|
||||||
|
|
||||||
|
// () | (a) | (a,) | (a, b)
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (next(t)[1]?.kind === terminator) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextValue;
|
||||||
|
[t, nextValue] = parser(t);
|
||||||
|
|
||||||
|
items.push(nextValue);
|
||||||
|
|
||||||
|
let comma;
|
||||||
|
[t, comma] = eat(t, ",");
|
||||||
|
if (!comma) {
|
||||||
|
// No comma? Fine, you don't like trailing commas.
|
||||||
|
// But this better be the end.
|
||||||
|
if (next(t)[1]?.kind !== terminator) {
|
||||||
|
unexpectedToken(next(t)[1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[t] = expectNext(t, terminator);
|
||||||
|
|
||||||
|
return [t, items];
|
||||||
|
}
|
||||||
|
|
||||||
function eat<T extends BaseToken>(
|
function eat<T extends BaseToken>(
|
||||||
t: Token[],
|
t: Token[],
|
||||||
kind: T["kind"]
|
kind: T["kind"]
|
||||||
|
|
@ -371,13 +425,13 @@ function eat<T extends BaseToken>(
|
||||||
function expectNext<T extends BaseToken>(
|
function expectNext<T extends BaseToken>(
|
||||||
t: Token[],
|
t: Token[],
|
||||||
kind: T["kind"]
|
kind: T["kind"]
|
||||||
): [Token[], T] {
|
): [Token[], T & Token] {
|
||||||
let tok;
|
let tok;
|
||||||
[t, tok] = next(t);
|
[t, tok] = next(t);
|
||||||
if (tok.kind !== kind) {
|
if (tok.kind !== kind) {
|
||||||
throw new CompilerError(`expected ${kind}, found ${tok.kind}`, tok.span);
|
throw new CompilerError(`expected ${kind}, found ${tok.kind}`, tok.span);
|
||||||
}
|
}
|
||||||
return [t, tok as unknown as T];
|
return [t, tok as unknown as T & Token];
|
||||||
}
|
}
|
||||||
|
|
||||||
function next(t: Token[]): [Token[], Token] {
|
function next(t: Token[]): [Token[], Token] {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
Resolution,
|
Resolution,
|
||||||
Ty,
|
Ty,
|
||||||
Type,
|
Type,
|
||||||
|
TypeDef,
|
||||||
tyIsUnit,
|
tyIsUnit,
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
|
|
||||||
|
|
@ -19,6 +20,9 @@ function printItem(item: Item): string {
|
||||||
case "function": {
|
case "function": {
|
||||||
return printFunction(item.node);
|
return printFunction(item.node);
|
||||||
}
|
}
|
||||||
|
case "type": {
|
||||||
|
return printTypeDef(item.node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,7 +31,18 @@ function printFunction(func: FunctionDef): string {
|
||||||
.map(({ name, type }) => `${name}: ${printType(type)}`)
|
.map(({ name, type }) => `${name}: ${printType(type)}`)
|
||||||
.join(", ");
|
.join(", ");
|
||||||
const ret = func.returnType ? `: ${printType(func.returnType)}` : "";
|
const ret = func.returnType ? `: ${printType(func.returnType)}` : "";
|
||||||
return `function ${func.name}(${args})${ret} = ${printExpr(func.body, 0)}`;
|
return `function ${func.name}(${args})${ret} = ${printExpr(func.body, 0)};`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printTypeDef(type: TypeDef): string {
|
||||||
|
const fields = type.fields.map(
|
||||||
|
({ name, type }) => `${ind(1)}${name.name}: ${printType(type)},`
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldPart =
|
||||||
|
type.fields.length === 0 ? "()" : `(\n${fields.join("\n")}\n)`;
|
||||||
|
|
||||||
|
return `type ${type.name} = ${fieldPart};`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function printExpr(expr: Expr, indent: number): string {
|
function printExpr(expr: Expr, indent: number): string {
|
||||||
|
|
@ -165,6 +180,9 @@ export function printTy(ty: Ty): string {
|
||||||
case "var": {
|
case "var": {
|
||||||
return `?${ty.index}`;
|
return `?${ty.index}`;
|
||||||
}
|
}
|
||||||
|
case "struct": {
|
||||||
|
return ty.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import {
|
||||||
TY_UNIT,
|
TY_UNIT,
|
||||||
TyFn,
|
TyFn,
|
||||||
Type,
|
Type,
|
||||||
|
TyStruct,
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { CompilerError, Span } from "./error";
|
import { CompilerError, Span } from "./error";
|
||||||
import { printTy } from "./printer";
|
import { printTy } from "./printer";
|
||||||
|
|
@ -61,18 +62,7 @@ function lowerAstTyBase(
|
||||||
): Ty {
|
): Ty {
|
||||||
switch (type.kind) {
|
switch (type.kind) {
|
||||||
case "ident": {
|
case "ident": {
|
||||||
const res = type.value.res!;
|
return lowerIdentTy(type.value);
|
||||||
switch (res.kind) {
|
|
||||||
case "local": {
|
|
||||||
throw new Error("Cannot resolve local here");
|
|
||||||
}
|
|
||||||
case "item": {
|
|
||||||
return typeOfItem(res.index);
|
|
||||||
}
|
|
||||||
case "builtin": {
|
|
||||||
return builtinAsTy(res.name, type.value.span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case "list": {
|
case "list": {
|
||||||
return {
|
return {
|
||||||
|
|
@ -112,6 +102,14 @@ export function typeck(ast: Ast): Ast {
|
||||||
|
|
||||||
return { kind: "fn", params: args, returnTy };
|
return { kind: "fn", params: args, returnTy };
|
||||||
}
|
}
|
||||||
|
case "type": {
|
||||||
|
const fields = item.node.fields.map<[string, Ty]>(({ name, type }) => [
|
||||||
|
name.name,
|
||||||
|
lowerAstTy(type),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { kind: "struct", name: item.node.name, fields };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,7 +120,7 @@ export function typeck(ast: Ast): Ast {
|
||||||
const res = ident.res!;
|
const res = ident.res!;
|
||||||
switch (res.kind) {
|
switch (res.kind) {
|
||||||
case "local": {
|
case "local": {
|
||||||
throw new Error("Cannot resolve local here");
|
throw new Error("Item type cannot refer to local variable");
|
||||||
}
|
}
|
||||||
case "item": {
|
case "item": {
|
||||||
return typeOfItem(res.index);
|
return typeOfItem(res.index);
|
||||||
|
|
@ -149,7 +147,7 @@ export function typeck(ast: Ast): Ast {
|
||||||
ty: fnTy.returnTy,
|
ty: fnTy.returnTy,
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
kind: "function",
|
...item,
|
||||||
node: {
|
node: {
|
||||||
name: item.node.name,
|
name: item.node.name,
|
||||||
params: item.node.params.map((arg, i) => ({
|
params: item.node.params.map((arg, i) => ({
|
||||||
|
|
@ -160,8 +158,34 @@ export function typeck(ast: Ast): Ast {
|
||||||
returnType,
|
returnType,
|
||||||
ty: fnTy,
|
ty: fnTy,
|
||||||
},
|
},
|
||||||
span: item.span,
|
};
|
||||||
id: item.id,
|
}
|
||||||
|
case "type": {
|
||||||
|
const fieldNames = new Set();
|
||||||
|
item.node.fields.forEach(({ name }) => {
|
||||||
|
if (fieldNames.has(name)) {
|
||||||
|
throw new CompilerError(
|
||||||
|
`type ${item.node.name} has a duplicate field: ${name.name}`,
|
||||||
|
name.span
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fieldNames.add(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ty = typeOfItem(item.id) as TyStruct;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
node: {
|
||||||
|
name: item.node.name,
|
||||||
|
fields: item.node.fields.map((field, i) => ({
|
||||||
|
name: field.name,
|
||||||
|
type: {
|
||||||
|
...field.type,
|
||||||
|
ty: ty.fields[i][1],
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue