Skip to content

Instantly share code, notes, and snippets.

@mattiamanzati
Created March 10, 2018 08:52
Show Gist options
  • Save mattiamanzati/e6141943fa1c965b6f3f6845f63edc3b to your computer and use it in GitHub Desktop.
Save mattiamanzati/e6141943fa1c965b6f3f6845f63edc3b 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 => all(left(x), right(x))
.chain(([l, r]) => all(getValue(l), getValue(r)))
.chain(([l, r]) => 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}`))
}
const seq = sequence(taskEither, array)
function compileNodeSequence(nodes: ReadonlyArray<ts.Node>): INodeExecutorResult<never, any>{
// TODO: when ExecutionContext will be immutable, we need to pass each written "x" to the next one
return x => seq(nodes.map(node => compileNode(node)(x))).map<[any, IExecutionContext]>(v => v.pop() || [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