Created
March 10, 2018 09:09
-
-
Save mattiamanzati/fb0680c27d00374528b3f67468e03e07 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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