cmudig / apigen

API Generator from TypeScript
BSD 3-Clause "New" or "Revised" License
3 stars 0 forks source link

Strucutre #18

Open lan-lyu opened 1 year ago

lan-lyu commented 1 year ago

Code Structure

  1. go through the file: parse the SourceFile and get Statements[]
  2. build IR: convert the Statements[] into the Internal Representation, replace type reference with exact types
  3. generate output: from Internal Representation, emit the output in typescript and python

Input & Output Example

For each step, the example input and output are:

step 1

input:

interface FieldDef {
  field: string
  type: "quantitative" | "ordinal"
}

type ValueDef<T> = {
  value: T
}

type PositionDef = FieldDef;
type ColorDef = FieldDef | ValueDef<string>
type PrimitiveMark = "bar" | "area" | "line";
type Mark = PrimitiveMark | {
  type: PrimitiveMark
}

interface Encoding {
  x?: PositionDef;
  y?: PositionDef;
  color?: ColorDef;
}

interface LayerSpec {
  layer: Spec[]
}

export type Spec = {
    mark: Mark
    data: string
    encode: Encoding
}

output:

Statements[9]

export class Statement {
    public name?: string;
    public kind: typeKind | undefined; 
    public type: typeType | undefined;
    public isGeneric: boolean = false;
    public members: Record<string, string> = {};
    public children: string[] | undefined;
}

[0]{
    name: "FieldDef"
    kind: 261 // create our own enum
    type: undefined
    isGeneric: false
    members: {"field": "string", "type": '"quantitative" | "ordinal"'}
    children:[]
}

[1]{
    name: "ValueDef"
    kind: 262
    type: 184 //TypeLiteral
    isGeneric: true
    members: {"value": "T"}
    children:[]
}

[2]{
    name: "PositionDef"
    kind: 262
    type: 180  //TypeReference
    isGeneric: false
    members: {"type": "FieldDef"}
    children:["FieldDef"]
}

[3]{
    name: "ColorDef"
    kind: 262
    type: 189 //UnionType
    isGeneric: false
    members: {"type": "FieldDef | ValueDef<string>"}
    children:["FieldDef", "ValueDef<string>"]
}

[4]{
    name: "PrimitiveMark"
    kind: 262
    type: 189 //UnionType
    isGeneric: false
    members: {"type": "bar | area | line"}
    children:[]
}

[5]{
    name: "Mark"
    kind: 262
    type: 189 //UnionType
    isGeneric: false
    members: {"type": "PrimitiveMark | { PrimitiveMark }"}
    children:["PrimitiveMark"]
}

[6]{
    name: "Encoding"
    kind: 261
    type: undefined
    isGeneric: false
    members: {"x?": "PositionDef", "y?": "PositionDef", "color?": "ColorDef"}
    children:["PositionDef", "ColorDef"]
}

[7]{
    name: "LayerSpec"
    kind: 261
    type: undefined
    isGeneric: false
    members: {"layer": "Spec[]"}
    children:["Spec"]
}

[8]{
    name: "Spec"
    kind: 262
    type: 184  //TypeLiteral
    isGeneric: false
    members: {"mark": "Mark", "data": "string", "encode": "Encoding"}
    children:["Mark", "Encoding"]
}

step 2

build a tree that the root node is Spec

output is a tree like this

spec ___ mark
         |___ data
         |___ encode ____ x
                     |____ y
                     |____ color

At the same time, replace the TypeReference with the exact type based on children of the statement. For example: turn mark's member into "type: bar | area | line | { bar| area | line}" input (statement we have in step 1)

[4]{
    name: "PrimitiveMark"
    kind: 262
    type: 189 //UnionType
    isGeneric: false
    members: {"type": "bar | area | line"}
    children:[]
}

[5]{
    name: "Mark"
    kind: 262
    type: 189 //UnionType
    isGeneric: false
    members: {"type": "PrimitiveMark | { PrimitiveMark }"}
    children:["PrimitiveMark"]
}

output (mark object in the tree)

{
    name: "Mark"
    kind: 262
    type: 189 //UnionType
    isGeneric: false
    members: {"type": "bar | area | line | { bar| area | line}"}
}

step 3

Traverse the tree and generate classes and functions

}

export function spec(mark: Mark, data: string, encode: Encoding){ return new Spec(mark, data, encode); }

class Mark { constructor(private type: "bar" | "area" | "line" | { type: "bar" | "area" | "line"}) {}

}

export function mark(type: "bar" | "area" | "line" | { type: "bar" | "area" | "line"}){ return new Mark(type); }

class Data { constructor(private type: string) {}

}

export function data(type: string) { return new Data(type); }

class X { constructor(private field?: string, private type?: "quantitative" | "ordinal") {}

}

export function x(field?: string, type?: "quantitative" | "ordinal"){ return new X(field, type); }

class Y { constructor(private field?: string, private type?: "quantitative" | "ordinal") {}

}

export function y(field?: string, type?: "quantitative" | "ordinal"){ return new Y(field, type); }

// TODO: optimize the color argument class Color { constructor(private field?: string, private type?: "quantitative" | "ordinal", private value?: string) {}

}

export function color(field?: string, type?: "quantitative" | "ordinal", value?: string){ return new Color(field, type, value); }

class Encoding { constructor(private x?: X, private y?: Y, private color?: Color) {}

}

export function encode(x?: X, y?: Y, color?: Color){ return new Encoding(x, y, color); }

export function toSpec(obj: any){ return obj; }

export function toJSON(obj: any){ return JSON.stringify(obj); }

lan-lyu commented 1 year ago

Should the tree be simple

spec ___ mark
         |___ data
         |___ encode ____ x
                     |____ y
                     |____ color

or having reference types as leaves, and creating methods for these reference types?

spec ___ mark
         |___ data
         |___ encode ____ x ____ position
                     |____ y ____ position
                     |____ color ____ field
                                 |____ value

If using the latter one, the final output will be like this

class Color extends BaseObject{
  // constructor(private field?: string, private type?: "quantitative" | "ordinal", private value?: string) {}
  constructor(...args) {
    super();
    init(this);
    assign(this, ...args);
  }

  field(field: {field:string, type:"quantitative" | "ordinal"} | string ){
    if (arguments.length) {
      const obj = copy(this);
      set(obj, "color", field);
      return obj;
    } else {
      return get(this, "color");
    }
  }
}