Skip to content

Instantly share code, notes, and snippets.

@mattiamanzati
Created March 10, 2018 09:09
Show Gist options
  • Save mattiamanzati/fb0680c27d00374528b3f67468e03e07 to your computer and use it in GitHub Desktop.
Save mattiamanzati/fb0680c27d00374528b3f67468e03e07 to your computer and use it in GitHub Desktop.
import * as ts from "typescript";
import { taskEither, TaskEither, left, right } from "fp-ts/lib/TaskEither";
import { task, Task } from "fp-ts/lib/Task";
import { left as eitherLeft, right as _eitherRight, Either } from "fp-ts/lib/Either";
import { sequence } from "fp-ts/lib/Traversable"
import { array } from "fp-ts/lib/Array"
export type INode = ts.StringLiteral | ts.NumericLiteral | ts.BinaryExpression | ts.ExpressionStatement
export type IValue = string | number | boolean | any
export type IReference = {
node: INode
base: IValue
propertyName: string
}
export type IScope = {
parent: IScope | null
value: IValue
}
export type IExecutionContext = {
scope: IScope
strict: boolean
result: IValue
withResult(v: IValue): IExecutionContext
}
export type IUnknownNodeError = {
type: 'UnknownNode',
node: any
}
export type INodeExecutor<N extends INode, E, A> = (n: N) => (x: IExecutionContext) => TaskEither<E | IUnknownNodeError, [A, IExecutionContext]>
export type INodeExecutorResult<E, A> = (x: IExecutionContext) => TaskEither<E| IUnknownNodeError, [A, IExecutionContext]>
export interface ICompileNode {
compileNode(node: INode): (x: IExecutionContext) => TaskEither<any, any>
}
export enum ExecutionContextType {
GLOBAL_CODE
}
// TaskEither.all
const all = <L, A, B>(fa: TaskEither<L, A>, fb: TaskEither<L, B>) => fb.ap(fa.map(a => (b: B) => [a, b])) as TaskEither<L, [A, B]>
const toVoid = (x: IExecutionContext) => <A>(a: A) => [undefined, x] as [void, IExecutionContext]
function eitherRight<A, B>(r: [A, B]): Either<never, [A, B]>{
return _eitherRight(r)
}
const newTaskEither = <L, A>(fn: () => Either<L, A>): TaskEither<L, A> => {
return new TaskEither(new Task(() => Promise.resolve(fn())))
}
function newTaskEitherOf<A, B>(r: [A, B]): TaskEither<never, [A, B]>{
return taskEither.of(r)
}
function newTaskEitherLeftOf<L>(r: L): TaskEither<L, never>{
return newTaskEither(() => eitherLeft(r))
}
function newUnknownNodeError(node: any): IUnknownNodeError{
return {type: 'UnknownNode', node}
}
// Scope
class Scope {
constructor(
public readonly value: IValue,
public readonly parent: IScope | null = null
){
}
}
const GLOBAL_OBJECT = {}
// ExecutionContext
class ExecutionContext implements IExecutionContext {
constructor(
public readonly type: ExecutionContextType,
public readonly strict: boolean = true,
public readonly scope: Scope = new Scope(GLOBAL_OBJECT, null),
public readonly result: any = undefined
){
}
withResult(r: any){
return new ExecutionContext(this.type, this.strict, this.scope, r)
}
}
// getValue
function getValue(r: any): TaskEither<never, IValue>{
// TODO: intercept Reference and resolve value
return r
}
// NumericLiteral
export const runNumericLiteral: INodeExecutor<
ts.NumericLiteral,
never,
number
> = node => x => newTaskEitherOf([+node.text, x]);
// StringLiteral
export const runStringLiteral: INodeExecutor<
ts.StringLiteral,
never,
string
> = node => x => newTaskEitherOf([node.text, x]);
// BinaryExpression
export const runBinaryExpression: INodeExecutor<
ts.BinaryExpression,
never,
IValue
> = node => {
const left = compileNode(node.left)
const right = compileNode(node.right)
return x =>
compileValueSequence([node.left, node.right])(x)
.chain(([values, x]) => {
const [l, r] = values
return newTaskEither(() => {
switch(node.operatorToken.kind){
case ts.SyntaxKind.LessThanToken: return eitherRight([l < r, x])
case ts.SyntaxKind.GreaterThanToken: return eitherRight([l > r, x])
case ts.SyntaxKind.LessThanEqualsToken: return eitherRight([l <= r, x])
case ts.SyntaxKind.GreaterThanEqualsToken: return eitherRight([l >= r, x])
case ts.SyntaxKind.EqualsEqualsToken: return eitherRight([l == r, x])
case ts.SyntaxKind.ExclamationEqualsToken: return eitherRight([l != r, x])
case ts.SyntaxKind.EqualsEqualsEqualsToken: return eitherRight([l === r, x])
case ts.SyntaxKind.ExclamationEqualsEqualsToken: return eitherRight([l !== r, x])
case ts.SyntaxKind.PlusToken: return eitherRight([l + r, x])
case ts.SyntaxKind.MinusToken: return eitherRight([l - r, x])
default: return eitherLeft(newUnknownNodeError(`Unknown operatorToken kind #${node.operatorToken.kind}`))
}
})
})
};
// ExpressionStatement
export const runExpressionStatement: INodeExecutor<
ts.ExpressionStatement,
never,
void
> = node => {
const expression = compileNode(node.expression)
return x => expression(x)
.chain(([v, x]) => all(getValue(v), taskEither.of(x)))
.map<[void, IExecutionContext]>(([v, x]) => [undefined, x.withResult(v)])
};
function compileNode(node: ts.NumericLiteral): INodeExecutorResult<never, number>
function compileNode(node: ts.StringLiteral): INodeExecutorResult<never, string>
function compileNode(node: ts.BinaryExpression): INodeExecutorResult<never, any>
function compileNode(node: ts.ExpressionStatement): INodeExecutorResult<never, void>
function compileNode(node: ts.Node): INodeExecutorResult<never, any>
function compileNode(node: ts.Node): INodeExecutorResult<never, any>{
if(ts.isStringLiteral(node)) return runStringLiteral(node)
if(ts.isNumericLiteral(node)) return runNumericLiteral(node)
if(ts.isBinaryExpression(node)) return runBinaryExpression(node)
if(ts.isExpressionStatement(node)) return runExpressionStatement(node)
return x => newTaskEitherLeftOf(newUnknownNodeError(`Unknown node kind #${node.kind}`))
}
function compileValueSequence(nodes: ReadonlyArray<ts.Node>): INodeExecutorResult<never, any[]>{
return x =>
nodes.reduce(
(t: TaskEither<any, any>, n) =>
t.chain(
([v, x]) => compileNode(n)(x).chain(([nv, nx]) => taskEither.of([v.concat(nv), nx]))
),
taskEither.of([[], x])
)
}
function compileNodeSequence(nodes: ReadonlyArray<ts.Node>): INodeExecutorResult<never, any>{
// plz, don't killz me for thiz cast to any!
return x => nodes.reduce((t: TaskEither<any, any>, n) => t.chain(([v, x]) => compileNode(n)(x)), taskEither.of([undefined, x]))
}
export function compileCode(code: string) {
// create the file source
const sourceFile = ts.createSourceFile(
"code.ts",
code,
ts.ScriptTarget.Latest,
true
);
// make the execution context
const x = new ExecutionContext(ExecutionContextType.GLOBAL_CODE, true);
// make the AST transforms
return compileNodeSequence(sourceFile.statements)(x)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment