Skip to content
Snippets Groups Projects
Commit 3fff4c0b authored by David Sehnal's avatar David Sehnal
Browse files

Added Mol-script

parent 184181c8
Branches
Tags
No related merge requests found
/*
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Expression from './expression'
import Environment from './runtime/environment'
import RuntimeExpression from './runtime/expression'
import SymbolRuntime, { RuntimeArguments } from './runtime/symbol'
export type CompiledExpression<C, T> = (ctx: C) => T
type Compiler<C> = <T>(expression: Expression) => CompiledExpression<C, T>
function Compiler<C>(envProvider: (ctx?: C) => Environment): Compiler<C> {
const env = envProvider(void 0);
return expression => wrap(envProvider, compile(env, expression).runtime);
}
type CompileResult = { isConst: boolean, runtime: RuntimeExpression }
namespace CompileResult {
export function Const(value: any): CompileResult { return { isConst: true, runtime: RuntimeExpression.constant(value) } }
export function Dynamic(runtime: RuntimeExpression): CompileResult { return { isConst: false, runtime } }
}
function wrap<C, T>(envProvider: (ctx?: C) => Environment, runtime: RuntimeExpression<C, T>) {
return (ctx: C) => runtime(envProvider(ctx));
}
function noRuntimeFor(symbol: string) {
throw new Error(`Could not find runtime for symbol '${symbol}'.`);
}
function applySymbolStatic(runtime: SymbolRuntime, args: RuntimeArguments) {
return CompileResult.Dynamic(env => runtime(env, args))
}
function applySymbolDynamic(head: RuntimeExpression, args: RuntimeArguments) {
return CompileResult.Dynamic(env => {
const value = head(env);
const symbol = env.symbolTable[value];
if (!symbol) noRuntimeFor(value);
return symbol.runtime(env, args);
})
}
function apply(env: Environment, head: CompileResult, args: RuntimeArguments, constArgs: boolean): CompileResult {
if (head.isConst) {
const value = head.runtime(env);
const symbol = env.symbolTable[value];
if (!symbol) throw new Error(`Could not find runtime for symbol '${value}'.`);
if (symbol.attributes.isStatic && constArgs) return CompileResult.Const(symbol.runtime(env, args));
return applySymbolStatic(symbol.runtime, args);
}
return applySymbolDynamic(head.runtime, args);
}
function compile(env: Environment, expression: Expression): CompileResult {
if (Expression.isLiteral(expression)) {
return CompileResult.Const(expression);
}
const head = compile(env, expression.head);
if (!expression.args) {
return apply(env, head, [], true);
} else if (Expression.isArgumentsArray(expression.args)) {
const args = [];
let constArgs = false;
for (const arg of expression.args) {
const compiled = compile(env, arg);
constArgs = constArgs && compiled.isConst;
args.push(compiled.runtime);
}
return apply(env, head, args as any, constArgs);
} else {
const args = Object.create(null);
let constArgs = false;
for (const key of Object.keys(expression.args)) {
const compiled = compile(env, expression.args[key]);
constArgs = constArgs && compiled.isConst;
args[key] = compiled.runtime;
}
return apply(env, head, args, constArgs);
}
}
export default Compiler
\ No newline at end of file
/*
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Expression from './expression'
interface Container {
source?: string,
version: string,
expression: Expression
}
export default Container
\ No newline at end of file
/*
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Type from './type'
import Symbol, { Arguments, Argument } from './symbol'
import { symbol, normalizeTable, symbolList } from './helpers'
export namespace Types {
export type List<T = any> = ArrayLike<T>
export type Set<T = any> = { has(e: T): boolean }
export const AnyVar = Type.Variable('a', Type.Any);
export const AnyValueVar = Type.Variable('a', Type.Any);
export const ConstrainedVar = Type.Variable('a', Type.Any, true);
export const Regex = Type.Value<RegExp>('Core', 'Regex');
export const Set = <T extends Type>(t?: T) => Type.Container<Set<T['@type']>>('Core', 'Set', t || AnyValueVar);
export const List = <T extends Type>(t?: T) => Type.Container<List<T['@type']>>('Core', 'List', t || AnyVar);
export const Fn = <T extends Type>(t?: T, alias?: string) => Type.Container<(env: any) => T['@type']>('Core', 'Fn', t || AnyVar, alias);
export const Flags = <T extends Type>(t: T, alias?: string) => Type.Container<number>('Core', 'Flags', t, alias);
export const BitFlags = Flags(Type.Num, 'BitFlags');
}
function unaryOp<T extends Type>(type: T, description?: string) {
return symbol(Arguments.Dictionary({ 0: Argument(type) }), type, description);
}
function binOp<T extends Type>(type: T, description?: string) {
return symbol(Arguments.List(type, { nonEmpty: true }), type, description);
}
function binRel<A extends Type, T extends Type>(src: A, target: T, description?: string) {
return symbol(Arguments.Dictionary({
0: Argument(src),
1: Argument(src)
}), target, description);
}
const type = {
'@header': 'Types',
bool: symbol(Arguments.Dictionary({ 0: Argument(Type.AnyValue) }), Type.Bool, 'Convert a value to boolean.'),
num: symbol(Arguments.Dictionary({ 0: Argument(Type.AnyValue) }), Type.Num, 'Convert a value to number.'),
str: symbol(Arguments.Dictionary({ 0: Argument(Type.AnyValue) }), Type.Str, 'Convert a value to string.'),
regex: symbol(
Arguments.Dictionary({
0: Argument(Type.Str, { description: 'Expression' }),
1: Argument(Type.Str, { isOptional: true, description: `Flags, e.g. 'i' for ignore case` })
}), Types.Regex, 'Creates a regular expression from a string using the ECMAscript syntax.'),
list: symbol(Arguments.List(Types.AnyVar), Types.List()),
set: symbol(Arguments.List(Types.AnyValueVar), Types.Set()),
bitflags: symbol(Arguments.Dictionary({ 0: Argument(Type.Num) }), Types.BitFlags, 'Interpret a number as bitflags.'),
compositeKey: symbol(Arguments.List(Type.AnyValue), Type.AnyValue),
};
const logic = {
'@header': 'Logic',
not: unaryOp(Type.Bool),
and: binOp(Type.Bool),
or: binOp(Type.Bool),
};
const ctrl = {
'@header': 'Control',
eval: symbol(Arguments.Dictionary({ 0: Argument(Types.Fn(Types.AnyVar)) }), Types.AnyVar, 'Evaluate a function.'),
fn: symbol(Arguments.Dictionary({ 0: Argument(Types.AnyVar) }), Types.Fn(Types.AnyVar), 'Wrap an expression to a "lazy" function.'),
if: symbol(Arguments.Dictionary({
0: Argument(Type.Bool, { description: 'Condition' }),
1: Argument(Type.Variable('a', Type.Any), { description: 'If true' }),
2: Argument(Type.Variable('b', Type.Any), { description: 'If false' })
}), Type.Union([Type.Variable('a', Type.Any), Type.Variable('b', Type.Any)])),
assoc: symbol(Arguments.Dictionary({
0: Argument(Type.Str, { description: 'Name' }),
1: Argument(Type.Variable('a', Type.Any), {description: 'Value to assign' })
}), Type.Variable('a', Type.Any))
};
const rel = {
'@header': 'Relational',
eq: binRel(Type.Variable('a', Type.AnyValue, true), Type.Bool),
neq: binRel(Type.Variable('a', Type.AnyValue, true), Type.Bool),
lt: binRel(Type.Num, Type.Bool),
lte: binRel(Type.Num, Type.Bool),
gr: binRel(Type.Num, Type.Bool),
gre: binRel(Type.Num, Type.Bool),
inRange: symbol(Arguments.Dictionary({
0: Argument(Type.Num, { description: 'Value to test' }),
1: Argument(Type.Num, { description: 'Minimum value' }),
2: Argument(Type.Num, { description: 'Maximum value' })
}), Type.Bool, 'Check if the value of the 1st argument is >= 2nd and <= 3rd.'),
};
const math = {
'@header': 'Math',
add: binOp(Type.Num),
sub: binOp(Type.Num),
mult: binOp(Type.Num),
div: binRel(Type.Num, Type.Num),
pow: binRel(Type.Num, Type.Num),
mod: binRel(Type.Num, Type.Num),
min: binOp(Type.Num),
max: binOp(Type.Num),
floor: unaryOp(Type.Num),
ceil: unaryOp(Type.Num),
roundInt: unaryOp(Type.Num),
abs: unaryOp(Type.Num),
sqrt: unaryOp(Type.Num),
sin: unaryOp(Type.Num),
cos: unaryOp(Type.Num),
tan: unaryOp(Type.Num),
asin: unaryOp(Type.Num),
acos: unaryOp(Type.Num),
atan: unaryOp(Type.Num),
sinh: unaryOp(Type.Num),
cosh: unaryOp(Type.Num),
tanh: unaryOp(Type.Num),
exp: unaryOp(Type.Num),
log: unaryOp(Type.Num),
log10: unaryOp(Type.Num),
atan2: binRel(Type.Num, Type.Num)
};
const str = {
'@header': 'Strings',
concat: binOp(Type.Str),
match: symbol(Arguments.Dictionary({ 0: Argument(Types.Regex), 1: Argument(Type.Str) }), Type.Bool)
};
const list = {
'@header': 'Lists',
getAt: symbol(Arguments.Dictionary({ 0: Argument(Types.List()), 1: Argument(Type.Num) }), Types.AnyVar)
};
const set = {
'@header': 'Sets',
has: symbol(Arguments.Dictionary({ 0: Argument(Types.Set(Types.ConstrainedVar)), 1: Argument(Types.ConstrainedVar) }), Type.Bool, 'Check if the the 1st argument includes the value of the 2nd.'),
isSubset: symbol(Arguments.Dictionary({ 0: Argument(Types.Set(Types.ConstrainedVar)), 1: Argument(Types.Set(Types.ConstrainedVar)) }), Type.Bool, 'Check if the the 1st argument is a subset of the 2nd.')
};
const flags = {
'@header': 'Flags',
hasAny: symbol(Arguments.Dictionary({
0: Argument(Types.Flags(Types.ConstrainedVar)),
1: Argument(Types.Flags(Types.ConstrainedVar))
}), Type.Bool, 'Check if the the 1st argument has at least one of the 2nd one\'s flags.'),
hasAll: symbol(Arguments.Dictionary({
0: Argument(Types.Flags(Types.ConstrainedVar)),
1: Argument(Types.Flags(Types.ConstrainedVar))
}), Type.Bool, 'Check if the the 1st argument has all 2nd one\'s flags.'),
}
const table = {
'@header': 'Language Primitives',
type,
logic,
ctrl,
rel,
math,
str,
list,
set,
flags
}
normalizeTable(table);
export const SymbolList = symbolList(table);
export const SymbolMap = (function() {
const map: { [id: string]: Symbol | undefined } = Object.create(null);
for (const s of SymbolList) map[s.id] = s;
return map;
})();
export default table;
/*
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Expression from './expression'
const { isLiteral, isArgumentsArray } = Expression;
export default function format(e: Expression) {
const writer = new Writer();
_format(e, writer);
return writer.getStr();
}
class Writer {
private value: string[] = [];
private currentLineLength = 0;
private prefixLength = 0;
private _prefix: string = '';
private localPrefix: string = '';
private setLocal() {
this.localPrefix = ' ';
}
newline() {
this.value.push(`\n${this._prefix}${this.localPrefix}`);
this.currentLineLength = 0;
}
push() {
this.value.push('(');
this.currentLineLength = 0;
this.localPrefix = '';
this.prefixLength += 2;
this._prefix = new Array(this.prefixLength + 1).join(' ');
}
pop() {
this.value.push(')');
this.prefixLength -= 2;
this._prefix = new Array(this.prefixLength + 1).join(' ');
}
append(str: string) {
if (!this.currentLineLength) {
this.value.push(str);
this.currentLineLength = str.length;
} else if (this.currentLineLength + this.prefixLength + this.localPrefix.length + str.length < 80) {
this.value.push(str);
this.currentLineLength += str.length;
} else {
this.setLocal();
this.newline();
this.value.push(str);
this.currentLineLength = str.length;
}
}
whitespace() {
if (this.currentLineLength + this.prefixLength + this.localPrefix.length + 1 < 80) {
this.value.push(' ');
}
}
getStr() {
return this.value.join('');
}
}
function _format(e: Expression, writer: Writer) {
if (isLiteral(e)) {
if (typeof e === 'string' && (/\s/.test(e) || !e.length)) writer.append(`\`${e}\``);
else writer.append(`${e}`);
return;
}
writer.push();
_format(e.head, writer);
if (!e.args) {
writer.pop();
return;
}
if (isArgumentsArray(e.args)) {
let prevLiteral = true;
for (const a of e.args) {
if (isLiteral(a)) {
if (prevLiteral) writer.whitespace();
else writer.newline();
prevLiteral = true;
} else {
prevLiteral = false;
writer.newline();
}
_format(a, writer);
}
writer.pop();
return;
}
const keys = Object.keys(e.args);
if (!keys.length) {
writer.pop();
return;
}
if (keys.length === 1 && isLiteral(e.args[keys[0]])) {
writer.whitespace()
writer.append(`:${keys[0]}`);
writer.whitespace();
_format(e.args[keys[0]], writer);
writer.pop();
return;
}
for (const a of keys) {
writer.newline();
writer.append(`:${a}`);
writer.whitespace();
_format(e.args[a], writer);
}
writer.pop();
}
\ No newline at end of file
/*
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
type Expression =
| Expression.Literal
| Expression.Apply
namespace Expression {
export type Literal = string | number | boolean
export type Arguments = Expression[] | { [name: string]: Expression }
export interface Apply { readonly head: Expression, readonly args?: Arguments }
export function Apply(head: Expression, args?: Arguments): Apply { return args ? { head, args } : { head }; }
export function isArgumentsArray(e: Arguments): e is Expression[] { return e instanceof Array; }
export function isArgumentsMap(e: Arguments): e is { [name: string]: Expression } { return !(e instanceof Array); }
export function isLiteral(e: Expression): e is Expression.Literal { return !isApply(e); }
export function isApply(e: Expression): e is Expression.Apply { return !! e && !!(e as Expression.Apply).head && typeof e === 'object'; }
}
export default Expression
\ No newline at end of file
/*
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Type from './type'
import Symbol, { Arguments, isSymbol } from './symbol'
export function symbol<A extends Arguments, T extends Type<S>, S>(args: A, type: T, description?: string) {
return Symbol('', args, type, description);
}
export function normalizeTable(table: any) {
_normalizeTable('', '', table);
}
export function symbolList(table: any): Symbol[] {
const list: Symbol[] = [];
_symbolList(table, list);
return list;
}
function formatKey(key: string) {
const regex = /([a-z])([A-Z])([a-z]|$)/g;
// do this twice because 'xXxX'
return key.replace(regex, (s, a, b, c) => `${a}-${b.toLocaleLowerCase()}${c}`).replace(regex, (s, a, b, c) => `${a}-${b.toLocaleLowerCase()}${c}`);
}
function _normalizeTable(namespace: string, key: string, obj: any) {
if (isSymbol(obj)) {
obj.info.namespace = namespace;
obj.info.name = obj.info.name || formatKey(key);
obj.id = `${obj.info.namespace}.${obj.info.name}`;
return;
}
const currentNs = `${obj['@namespace'] || formatKey(key)}`;
const newNs = namespace ? `${namespace}.${currentNs}` : currentNs;
for (const childKey of Object.keys(obj)) {
if (typeof obj[childKey] !== 'object' && !isSymbol(obj[childKey])) continue;
_normalizeTable(newNs, childKey, obj[childKey]);
}
}
function _symbolList(obj: any, list: Symbol[]) {
if (isSymbol(obj)) {
list.push(obj);
return;
}
for (const childKey of Object.keys(obj)) {
if (typeof obj[childKey] !== 'object' && !isSymbol(obj[childKey])) continue;
_symbolList(obj[childKey], list);
}
}
\ No newline at end of file
/*
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { SymbolRuntimeTable } from './symbol'
interface Environment<T = any> {
readonly symbolTable: SymbolRuntimeTable,
readonly context: T
}
export default Environment
\ No newline at end of file
/*
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Environment from './environment'
type RuntimeExpression<C = any, T = any> = (env: Environment<C>) => T
export interface ExpressionInfo {
isConst?: boolean
}
namespace RuntimeExpression {
export function constant<C, T>(c: T): RuntimeExpression<C, T> {
return env => c;
}
export function func<C, T>(f: (env: Environment<C>) => T): RuntimeExpression<C, T> {
return env => f(env);
}
}
export default RuntimeExpression
\ No newline at end of file
/*
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Environment from './environment'
import RuntimeExpression from './expression'
export type RuntimeArguments = ArrayLike<RuntimeExpression> | { [name: string]: RuntimeExpression | undefined }
type SymbolRuntime = (env: Environment, args: RuntimeArguments) => any
namespace SymbolRuntime {
export interface Info {
readonly runtime: SymbolRuntime,
readonly attributes: Attributes
}
export interface Attributes { isStatic: boolean }
}
function SymbolRuntime(symbol: Symbol, attributes: Partial<SymbolRuntime.Attributes> = {}) {
const { isStatic = false } = attributes;
return (runtime: SymbolRuntime): SymbolRuntime.Info => {
return ({ runtime, attributes: { isStatic } });
};
}
export type SymbolRuntimeTable = { readonly [id: string]: SymbolRuntime.Info }
export default SymbolRuntime
\ No newline at end of file
/*
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Type from './type'
import Expression from './expression'
export type Argument<T extends Type = Type> = {
type: T,
isOptional: boolean,
isRest: boolean,
defaultValue: T['@type'] | undefined,
description: string | undefined
}
export function Argument<T extends Type>(type: T, params?: { description?: string, defaultValue?: T['@type'], isOptional?: boolean, isRest?: boolean }): Argument<T> {
const { description = void 0, isOptional = false, isRest = false, defaultValue = void 0 } = params || {}
return { type, isOptional, isRest, defaultValue, description };
}
export type Arguments<T extends { [key: string]: any } = {}> =
| Arguments.List<T>
| Arguments.Dictionary<T>
export namespace Arguments {
export const None: Arguments = Dictionary({});
export interface Dictionary<T extends { [key: string]: any } = {}, Traits = {}> {
kind: 'dictionary',
map: { [P in keyof T]: Argument<T[P]> },
'@type': T
}
export function Dictionary<Map extends { [key: string]: Argument<any> }>(map: Map): Arguments<{ [P in keyof Map]: Map[P]['type']['@type'] }> {
return { kind: 'dictionary', map, '@type': 0 as any };
}
export interface List<T extends { [key: string]: any } = {}, Traits = {}> {
kind: 'list',
type: Type,
nonEmpty: boolean,
'@type': T
}
export function List<T extends Type>(type: T, params?: { nonEmpty?: boolean }): Arguments<{ [key: string]: T['@type'] }> {
const { nonEmpty = false } = params || { }
return { kind: 'list', type, nonEmpty, '@type': 0 as any };
}
}
export type ExpressionArguments<T> = { [P in keyof T]?: Expression } | { [index: number]: Expression }
interface Symbol<A extends Arguments = Arguments, T extends Type = Type> {
(args?: ExpressionArguments<A['@type']>): Expression,
info: {
namespace: string,
name: string,
description?: string
},
args: A
type: T,
id: string,
}
function Symbol<A extends Arguments, T extends Type>(name: string, args: A, type: T, description?: string) {
const symbol: Symbol<A, T> = function(args: ExpressionArguments<A['@type']>) {
return Expression.Apply(symbol.id, args as any);
} as any;
symbol.info = { namespace: '', name, description };
symbol.id = '';
symbol.args = args;
symbol.type = type;
return symbol;
}
export function isSymbol(x: any): x is Symbol {
const s = x as Symbol;
return typeof s === 'function' && !!s.info && !!s.args && typeof s.info.namespace === 'string' && !!s.type;
}
export type SymbolMap = { [id: string]: Symbol | undefined }
export default Symbol
/*
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
type Type<T = any> =
| Type.Any | Type.AnyValue | Type.Variable<T> | Type.Value<T>
| Type.Container<T> | Type.Union<T> | Type.OneOf<T>
namespace Type {
export interface Any { kind: 'any', '@type': any }
export interface Variable<T> { kind: 'variable', name: string, type: Type, isConstraint: boolean, '@type': any }
export interface AnyValue { kind: 'any-value', '@type': any }
export interface Value<T> { kind: 'value', namespace: string, name: string, parent?: Value<any>, '@type': T }
export interface Identifier { kind: 'identifier', '@type': string }
export interface Container<T> { kind: 'container', namespace: string, name: string, alias?: string, child: Type, '@type': T }
export interface Union<T> { kind: 'union', types: Type[], '@type': T }
export interface OneOf<T> { kind: 'oneof', type: Value<T>, namespace: string, name: string, values: { [v: string]: boolean | undefined }, '@type': T }
export function Variable<T = any>(name: string, type: Type, isConstraint?: boolean): Variable<T> { return { kind: 'variable', name, type: type, isConstraint } as any; }
export function Value<T>(namespace: string, name: string, parent?: Value<any>): Value<T> { return { kind: 'value', namespace, name, parent } as any; }
export function Container<T = any>(namespace: string, name: string, child: Type, alias?: string): Container<T> { return { kind: 'container', namespace, name, child, alias } as any; }
export function Union<T = any>(types: Type[]): Union<T> { return { kind: 'union', types } as any; }
export function OneOf<T = any>(namespace: string, name: string, type: Value<T>, values: any[]): OneOf<T> {
const vals = Object.create(null);
for (const v of values) vals[v] = true;
return { kind: 'oneof', namespace, name, type, values: vals } as any;
}
export const Any: Any = { kind: 'any' } as any;
export const AnyValue: AnyValue = { kind: 'any-value' } as any;
export const Num = Value<number>('', 'Number');
export const Str = Value<string>('', 'String');
export const Bool = OneOf<boolean>('', 'Bool', Str as any, ['true', 'false']);
export function oneOfValues({ values }: OneOf<any>) { return Object.keys(values).sort(); }
}
export default Type
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment