Typescript support
This commit is contained in:
parent
63c52d6b46
commit
5b1b535644
|
@ -1,29 +1,21 @@
|
|||
import {
|
||||
isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems, anonymousTemplate,
|
||||
isToken, isTokenType, replace, commaJoin, startPos, fixPos, joinItems,
|
||||
anonymousTemplate, laxRead,
|
||||
|
||||
Items, Pattern, Templates, Substitution, TokenType,
|
||||
SourceMap, StringScanner, LaxReader, CodeWriter, TemplateFunction, Token,
|
||||
SourceMap, CodeWriter, TemplateFunction, Token, itemText,
|
||||
} from '../syntax/index.js';
|
||||
import {
|
||||
FacetAction, Statement,
|
||||
SyndicateParser, SyndicateTypedParser,
|
||||
Identifier,
|
||||
FacetAction,
|
||||
Statement,
|
||||
ActivationImport,
|
||||
FacetFields,
|
||||
Binder,
|
||||
|
||||
compilePattern,
|
||||
patternText,
|
||||
|
||||
spawn,
|
||||
fieldDeclarationStatement,
|
||||
assertionEndpointStatement,
|
||||
dataflowStatement,
|
||||
eventHandlerEndpointStatement,
|
||||
duringStatement,
|
||||
typeDefinitionStatement,
|
||||
messageSendStatement,
|
||||
reactStatement,
|
||||
bootStatement,
|
||||
stopStatement,
|
||||
Identifier,
|
||||
activationImport,
|
||||
ActivationImport,
|
||||
} from './grammar.js';
|
||||
import {
|
||||
BootProc,
|
||||
|
@ -64,14 +56,17 @@ export interface ActivationRecord {
|
|||
}
|
||||
|
||||
export class ExpansionContext {
|
||||
readonly parser: SyndicateParser;
|
||||
readonly moduleType: ModuleType;
|
||||
readonly activationRecords: Array<ActivationRecord> = [];
|
||||
hasBootProc: boolean = false;
|
||||
readonly typescript: boolean;
|
||||
_collectedFields: FacetFields | null = null;
|
||||
|
||||
constructor(moduleType: ModuleType,
|
||||
typescript: boolean)
|
||||
{
|
||||
this.parser = typescript ? new SyndicateTypedParser : new SyndicateParser();
|
||||
this.moduleType = moduleType;
|
||||
this.typescript = typescript;
|
||||
}
|
||||
|
@ -80,9 +75,57 @@ export class ExpansionContext {
|
|||
return this.typescript ? anonymousTemplate`${name}: ${type}` : name;
|
||||
}
|
||||
|
||||
get thisFacetDecl(): Substitution {
|
||||
return this.argDecl('thisFacet', '__SYNDICATE__.Facet');
|
||||
get collectedFields(): FacetFields {
|
||||
if (this._collectedFields === null) {
|
||||
throw new Error("Internal error: this.collectedFields === null");
|
||||
}
|
||||
return this._collectedFields;
|
||||
}
|
||||
|
||||
collectField(f: Binder) {
|
||||
this.collectedFields.push(f);
|
||||
}
|
||||
|
||||
withCollectedFields<T>(fs: FacetFields, f: () => T): T {
|
||||
const oldCollectedFields = this._collectedFields;
|
||||
try {
|
||||
this._collectedFields = fs;
|
||||
return f();
|
||||
} finally {
|
||||
this._collectedFields = oldCollectedFields;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyId(i: Identifier): Items {
|
||||
return [ { ... i, type: TokenType.STRING, text: JSON.stringify(i.text) } ];
|
||||
}
|
||||
|
||||
function facetFieldObjectType(t: TemplateFunction, fs: FacetFields): Substitution {
|
||||
function formatBinder(binder: Binder) {
|
||||
const hasType = (binder.type !== void 0);
|
||||
return t`${[binder.id]}${hasType ? ': ': ''}${binder.type ?? ''}`;
|
||||
}
|
||||
return t`{${commaJoin(fs.map(formatBinder))}}`;
|
||||
}
|
||||
|
||||
function binderTypeGuard(t: TemplateFunction): (binder: Binder) => Items {
|
||||
return (binder) => {
|
||||
if (binder.type === void 0) {
|
||||
return t`${`/* ${binder.id.text} is a plain Value */`}`;
|
||||
} else {
|
||||
const typeText = itemText(binder.type);
|
||||
switch (typeText) {
|
||||
case 'boolean':
|
||||
case 'string':
|
||||
case 'number':
|
||||
case 'symbol':
|
||||
return t`if (typeof (${[binder.id]}) !== ${JSON.stringify(typeText)}) return;\n`;
|
||||
default:
|
||||
throw new Error(`Unhandled binding type: ${JSON.stringify(typeText)}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function expand(tree: Items, ctx: ExpansionContext): Items {
|
||||
|
@ -90,7 +133,7 @@ export function expand(tree: Items, ctx: ExpansionContext): Items {
|
|||
|
||||
function terminalWrap(t: TemplateFunction, isTerminal: boolean, body: Statement): Statement {
|
||||
if (isTerminal) {
|
||||
return t`thisFacet._stop(function (${ctx.thisFacetDecl}) {${body}})`
|
||||
return t`thisFacet._stop(function (thisFacet) {${body}})`
|
||||
} else {
|
||||
return body;
|
||||
}
|
||||
|
@ -107,44 +150,42 @@ export function expand(tree: Items, ctx: ExpansionContext): Items {
|
|||
const walk = (tree: Items): Items => expand(tree, ctx);
|
||||
const maybeWalk = (tree?: Items) : Items | undefined => (tree === void 0) ? tree : walk(tree);
|
||||
|
||||
xf(duringStatement, (s, t) => {
|
||||
xf(ctx.parser.duringStatement, (s, t) => {
|
||||
// TODO: spawn during
|
||||
const sa = compilePattern(s.pattern);
|
||||
return t`withSelfDo(function (${ctx.thisFacetDecl}) {
|
||||
const body = ctx.withCollectedFields(s.facetFields, () => walk(s.body));
|
||||
return t`withSelfDo(function (thisFacet) {
|
||||
const _Facets = new __SYNDICATE__.Dictionary();
|
||||
on asserted ${patternText(s.pattern)} => react {
|
||||
_Facets.set([${commaJoin(sa.captureIds.map(t=>[t]))}], thisFacet);
|
||||
_Facets.set([${commaJoin(sa.captureBinders.map(t=>[t.id]))}], thisFacet);
|
||||
dataflow void 0; // TODO: horrible hack to keep the facet alive if no other endpoints
|
||||
${s.body}
|
||||
${body}
|
||||
}
|
||||
on retracted ${patternText(s.pattern)} => {
|
||||
const _Key = [${commaJoin(sa.captureIds.map(t=>[t]))}];
|
||||
const _Key = [${commaJoin(sa.captureBinders.map(t=>[t.id]))}];
|
||||
_Facets.get(_Key)._stop();
|
||||
_Facets.delete(_Key);
|
||||
}
|
||||
});`;
|
||||
});
|
||||
|
||||
xf(spawn, (s, t) => {
|
||||
let proc = t`function (${ctx.thisFacetDecl}) {${walk(s.bootProcBody)}}`;
|
||||
xf(ctx.parser.spawn, (s, t) => {
|
||||
let body = ctx.withCollectedFields(s.facetFields, () => walk(s.body));
|
||||
let proc = t`function (thisFacet) {${body}}`;
|
||||
if (s.isDataspace) proc = t`__SYNDICATE__.inNestedDataspace(${proc})`;
|
||||
let assertions = (s.initialAssertions.length > 0)
|
||||
? t`, new __SYNDICATE__.Set([${commaJoin(s.initialAssertions.map(walk))}])`
|
||||
: ``;
|
||||
return t`_spawn(${maybeWalk(s.name) ?? 'null'}, ${proc}${assertions});`;
|
||||
let fieldTypeParam = ctx.typescript ? t`<${facetFieldObjectType(t, s.facetFields)}>` : '';
|
||||
return t`_spawn${fieldTypeParam}(${maybeWalk(s.name) ?? 'null'}, ${proc}${assertions});`;
|
||||
});
|
||||
|
||||
xf(fieldDeclarationStatement, (s, t) => {
|
||||
const prop = ('name' in s.property)
|
||||
? [ { start: s.property.name.start,
|
||||
end: s.property.name.end,
|
||||
type: TokenType.STRING,
|
||||
text: JSON.stringify(s.property.name.text) } ]
|
||||
: walk(s.property.expr);
|
||||
return t`declareField(${walk(s.target)}, ${prop}, ${maybeWalk(s.init) ?? 'void 0'});`;
|
||||
xf(ctx.parser.fieldDeclarationStatement, (s, t) => {
|
||||
ctx.collectField(s.property);
|
||||
return t`declareField(this, ${stringifyId(s.property.id)}, ${maybeWalk(s.init) ?? 'void 0'});`;
|
||||
});
|
||||
|
||||
xf(assertionEndpointStatement, (s, t) => {
|
||||
xf(ctx.parser.assertionEndpointStatement, (s, t) => {
|
||||
if (s.test == void 0) {
|
||||
return t`addEndpoint(thisFacet => ({ assertion: ${walk(s.template)}, analysis: null }));`;
|
||||
} else {
|
||||
|
@ -154,17 +195,18 @@ export function expand(tree: Items, ctx: ExpansionContext): Items {
|
|||
}
|
||||
});
|
||||
|
||||
xf(dataflowStatement, (s, t) => t`addDataflow(function (${ctx.thisFacetDecl}) {${walk(s.body)}});`);
|
||||
xf(ctx.parser.dataflowStatement, (s, t) =>
|
||||
t`addDataflow(function (thisFacet) {${walk(s.body)}});`);
|
||||
|
||||
xf(eventHandlerEndpointStatement, (s, t) => {
|
||||
xf(ctx.parser.eventHandlerEndpointStatement, (s, t) => {
|
||||
switch (s.triggerType) {
|
||||
case 'dataflow':
|
||||
return t`withSelfDo(function (${ctx.thisFacetDecl}) { dataflow { if (${walk(s.predicate)}) { ${terminalWrap(t, s.terminal, walk(s.body))} } } });`;
|
||||
return t`withSelfDo(function (thisFacet) { dataflow { if (${walk(s.predicate)}) { ${terminalWrap(t, s.terminal, walk(s.body))} } } });`;
|
||||
|
||||
case 'start':
|
||||
case 'stop': {
|
||||
const m = s.triggerType === 'start' ? 'addStartScript' : 'addStopScript';
|
||||
return t`${m}(function (${ctx.thisFacetDecl}) {${walk(s.body)}});`;
|
||||
return t`${m}(function (thisFacet) {${walk(s.body)}});`;
|
||||
}
|
||||
|
||||
case 'asserted':
|
||||
|
@ -176,6 +218,8 @@ export function expand(tree: Items, ctx: ExpansionContext): Items {
|
|||
'retracted': 'REMOVED',
|
||||
'message': 'MESSAGE',
|
||||
})[s.triggerType];
|
||||
const destructure = sa.captureBinders.length === 0 ? '__vs'
|
||||
: t`[${commaJoin(sa.captureBinders.map(i=>[i.id]))}]`;
|
||||
return t`addEndpoint(thisFacet => ({
|
||||
assertion: __SYNDICATE__.Observe(${walk(sa.assertion)}),
|
||||
analysis: {
|
||||
|
@ -183,8 +227,9 @@ export function expand(tree: Items, ctx: ExpansionContext): Items {
|
|||
constPaths: ${JSON.stringify(sa.constPaths)},
|
||||
constVals: [${commaJoin(sa.constVals.map(walk))}],
|
||||
capturePaths: ${JSON.stringify(sa.capturePaths)},
|
||||
callback: thisFacet.wrap((thisFacet, __Evt, [${commaJoin(sa.captureIds.map(i=>[i]))}]) => {
|
||||
callback: thisFacet.wrap((thisFacet, __Evt, ${destructure}) => {
|
||||
if (__Evt === __SYNDICATE__.Skeleton.EventType.${expectedEvt}) {
|
||||
${ctx.typescript ? joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n') : ''}
|
||||
thisFacet.scheduleScript(() => {${terminalWrap(t, s.terminal, walk(s.body))}});
|
||||
}
|
||||
})
|
||||
|
@ -194,17 +239,23 @@ export function expand(tree: Items, ctx: ExpansionContext): Items {
|
|||
}
|
||||
});
|
||||
|
||||
x(typeDefinitionStatement, (s, t) => {
|
||||
x(ctx.parser.typeDefinitionStatement, (s, t) => {
|
||||
const l = JSON.stringify(s.label.text);
|
||||
const fs = JSON.stringify(s.fields.map(f => f.text));
|
||||
const fs = JSON.stringify(s.fields.map(f => f.id.text));
|
||||
return t`const ${[s.label]} = __SYNDICATE__.Record.makeConstructor(${maybeWalk(s.wireName) ?? l}, ${fs});`;
|
||||
});
|
||||
|
||||
xf(messageSendStatement, (s, t) => t`_send(${walk(s.expr)});`);
|
||||
xf(ctx.parser.messageSendStatement, (s, t) => t`_send(${walk(s.expr)});`);
|
||||
|
||||
xf(reactStatement, (s, t) => t`addChildFacet(function (${ctx.thisFacetDecl}) {${walk(s.body)}});`);
|
||||
xf(ctx.parser.reactStatement, (s, t) => {
|
||||
const body = ctx.withCollectedFields(s.facetFields, () => walk(s.body));
|
||||
const fieldTypeParam = ctx.typescript
|
||||
? t`<${facetFieldObjectType(t, ctx.collectedFields)}, ${facetFieldObjectType(t, s.facetFields)}>`
|
||||
: '';
|
||||
return t`addChildFacet${fieldTypeParam}(function (thisFacet) {${body}});`;
|
||||
});
|
||||
|
||||
x(activationImport, (s, t) => {
|
||||
x(ctx.parser.activationImport, (s) => {
|
||||
const activationScriptId: Token = {
|
||||
start: s.activationKeyword.start,
|
||||
end: s.activationKeyword.end,
|
||||
|
@ -215,36 +266,39 @@ export function expand(tree: Items, ctx: ExpansionContext): Items {
|
|||
return [];
|
||||
}),
|
||||
|
||||
x(bootStatement, (s, t) => {
|
||||
x(ctx.parser.bootStatement, (s, t) => {
|
||||
ctx.hasBootProc = true;
|
||||
const activationStatements = ctx.activationRecords.map(({ activationScriptId: id }) =>
|
||||
t`thisFacet.activate(${[id]}); `);
|
||||
const body = t`${joinItems(activationStatements)}${walk(s)}`;
|
||||
const facetDecl = ctx.typescript ? 'thisFacet: __SYNDICATE__.Facet<{}>' : 'thisFacet';
|
||||
switch (ctx.moduleType) {
|
||||
case 'es6':
|
||||
return t`export function ${BootProc}(${ctx.thisFacetDecl}) {${body}}`;
|
||||
return t`export function ${BootProc}(${facetDecl}) {${body}}`;
|
||||
case 'require':
|
||||
return t`module.exports.${BootProc} = function (${ctx.thisFacetDecl}) {${body}};`;
|
||||
return t`module.exports.${BootProc} = function (${facetDecl}) {${body}};`;
|
||||
case 'global':
|
||||
return t`function ${BootProc}(${ctx.thisFacetDecl}) {${body}}`;
|
||||
return t`function ${BootProc}(${facetDecl}) {${body}}`;
|
||||
}
|
||||
});
|
||||
|
||||
xf(stopStatement, (s, t) => t`_stop(function (${ctx.thisFacetDecl}) {${walk(s.body)}});`)
|
||||
xf(ctx.parser.stopStatement, (s, t) =>
|
||||
t`_stop(function (thisFacet) {${walk(s.body)}});`)
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
export function compile(options: CompileOptions): CompilerOutput {
|
||||
const inputFilename = options.name ?? '/dev/stdin';
|
||||
|
||||
console.info(`Syndicate: compiling ${inputFilename}`);
|
||||
|
||||
const source = options.source;
|
||||
const moduleType = options.module ?? 'es6';
|
||||
const typescript = options.typescript ?? false;
|
||||
|
||||
const start = startPos(inputFilename);
|
||||
const scanner = new StringScanner(start, source);
|
||||
const reader = new LaxReader(scanner);
|
||||
let tree = stripShebang(reader.readToEnd());
|
||||
let tree = stripShebang(laxRead(source, { start, extraDelimiters: ':' }));
|
||||
const end = tree.length > 0 ? tree[tree.length - 1].end : start;
|
||||
|
||||
let macro = new Templates();
|
||||
|
|
|
@ -11,129 +11,49 @@ import {
|
|||
import * as Matcher from '../syntax/matcher.js';
|
||||
import { Path, Skeleton } from './internals.js';
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// AST types
|
||||
|
||||
export type Expr = Items;
|
||||
export type Statement = Items;
|
||||
export type Identifier = Token;
|
||||
|
||||
export const block = (acc: Items) => group('{', map(rest, items => acc.push(... items)));
|
||||
|
||||
export const statementBoundary = alt<any>(atom(';'), Matcher.newline);
|
||||
export const exprBoundary = alt<any>(atom(';'), atom(','), group('{', discard), Matcher.end);
|
||||
|
||||
export const identifier: Pattern<Identifier> = atom();
|
||||
|
||||
export function expr(... extraStops: Pattern<any>[]): Pattern<Expr> {
|
||||
return withoutSpace(upTo(alt(exprBoundary, ... extraStops)));
|
||||
}
|
||||
|
||||
export function statement(acc: Items): Pattern<any> {
|
||||
return alt<any>(block(acc),
|
||||
withoutSpace(seq(map(upTo(statementBoundary), items => acc.push(... items)),
|
||||
map(statementBoundary, i => i ? acc.push(i) : void 0))));
|
||||
}
|
||||
export type Type = Items;
|
||||
export type Binder = { id: Identifier, type?: Type };
|
||||
|
||||
export interface FacetAction {
|
||||
implicitFacet: boolean;
|
||||
}
|
||||
|
||||
export function facetAction<I extends FacetAction, T extends I>(
|
||||
pattern: (scope: T) => Pattern<any>): Pattern<T>
|
||||
{
|
||||
return i => {
|
||||
const scope = Object.create(null);
|
||||
scope.implicitFacet = true;
|
||||
const p = seq(option(map(atom('.'), _ => scope.implicitFacet = false)), pattern(scope));
|
||||
const r = p(i);
|
||||
if (r === null) return null;
|
||||
return [scope, r[1]];
|
||||
};
|
||||
export type FacetFields = Binder[];
|
||||
|
||||
export interface FacetProducingAction extends FacetAction {
|
||||
body: Statement;
|
||||
facetFields: FacetFields;
|
||||
}
|
||||
|
||||
export interface SpawnStatement extends FacetAction {
|
||||
export interface SpawnStatement extends FacetProducingAction {
|
||||
isDataspace: boolean;
|
||||
name?: Expr;
|
||||
initialAssertions: Expr[];
|
||||
parentIds: Identifier[];
|
||||
parentBinders: Binder[];
|
||||
parentInits: Expr[];
|
||||
bootProcBody: Statement;
|
||||
}
|
||||
|
||||
export const spawn: Pattern<SpawnStatement> & { headerExpr: Pattern<Expr> } =
|
||||
Object.assign(facetAction((o: SpawnStatement) => {
|
||||
o.isDataspace = false;
|
||||
o.initialAssertions = [];
|
||||
o.parentIds = [];
|
||||
o.parentInits = [];
|
||||
o.bootProcBody = [];
|
||||
return seq(atom('spawn'),
|
||||
option(seq(atom('dataspace'), exec(() => o.isDataspace = true))),
|
||||
option(seq(atom('named'),
|
||||
bind(o, 'name', spawn.headerExpr))),
|
||||
repeat(alt(seq(atom(':asserting'),
|
||||
map(spawn.headerExpr, e => o.initialAssertions.push(e))),
|
||||
map(scope((l: { id: Identifier, init: Expr }) =>
|
||||
seq(atom(':let'),
|
||||
bind(l, 'id', identifier),
|
||||
atom('='),
|
||||
bind(l, 'init', spawn.headerExpr))),
|
||||
l => {
|
||||
o.parentIds.push(l.id);
|
||||
o.parentInits.push(l.init);
|
||||
}))),
|
||||
block(o.bootProcBody));
|
||||
}), {
|
||||
headerExpr: expr(atom(':asserting'), atom(':let')),
|
||||
});
|
||||
|
||||
export interface FieldDeclarationStatement extends FacetAction {
|
||||
target: Expr;
|
||||
property: { name: Identifier } | { expr: Expr };
|
||||
property: Binder;
|
||||
init?: Expr;
|
||||
}
|
||||
|
||||
// Principal: Dataspace, but only for implementation reasons, so really Facet
|
||||
export const fieldDeclarationStatement: Pattern<FieldDeclarationStatement> =
|
||||
facetAction(o => {
|
||||
const prop = alt(seq(atom('.'), map(identifier, name => o.property = {name})),
|
||||
seq(group('[', map(expr(), expr => o.property = {expr}))));
|
||||
return seq(atom('field'),
|
||||
bind(o, 'target', expr(seq(prop, alt(atom('='), statementBoundary)))),
|
||||
prop,
|
||||
option(seq(atom('='), bind(o, 'init', expr()))),
|
||||
statementBoundary);
|
||||
});
|
||||
|
||||
export interface AssertionEndpointStatement extends FacetAction {
|
||||
isDynamic: boolean,
|
||||
template: Expr,
|
||||
test?: Expr,
|
||||
}
|
||||
|
||||
// Principal: Facet
|
||||
export const assertionEndpointStatement: Pattern<AssertionEndpointStatement> =
|
||||
facetAction(o => {
|
||||
o.isDynamic = true;
|
||||
return seq(atom('assert'),
|
||||
option(map(atom(':snapshot'), _ => o.isDynamic = false)),
|
||||
bind(o, 'template', expr(seq(atom('when'), group('(', discard)))),
|
||||
option(seq(atom('when'), group('(', bind(o, 'test', expr())))),
|
||||
statementBoundary);
|
||||
});
|
||||
|
||||
export interface StatementFacetAction extends FacetAction {
|
||||
body: Statement;
|
||||
}
|
||||
|
||||
export function blockFacetAction(kw: Pattern<any>): Pattern<StatementFacetAction> {
|
||||
return facetAction(o => {
|
||||
o.body = [];
|
||||
return seq(kw, block(o.body));
|
||||
});
|
||||
}
|
||||
|
||||
// Principal: Facet
|
||||
export const dataflowStatement = blockFacetAction(atom('dataflow'));
|
||||
|
||||
export interface GenericEventEndpointStatement extends StatementFacetAction {
|
||||
terminal: boolean;
|
||||
isDynamic: boolean;
|
||||
|
@ -156,115 +76,35 @@ export interface AssertionEventEndpointStatement extends GenericEventEndpointSta
|
|||
export type EventHandlerEndpointStatement =
|
||||
DataflowEndpointStatement | PseudoEventEndpointStatement | AssertionEventEndpointStatement;
|
||||
|
||||
export function mandatoryIfNotTerminal(o: GenericEventEndpointStatement, p: Pattern<any>): Pattern<any> {
|
||||
return i => {
|
||||
return (o.terminal) ? option(p)(i) : p(i);
|
||||
};
|
||||
}
|
||||
|
||||
// Principal: Facet
|
||||
export const eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> =
|
||||
facetAction(o => {
|
||||
o.terminal = false;
|
||||
o.isDynamic = true;
|
||||
o.body = [];
|
||||
return seq(option(map(atom('stop'), _ => o.terminal = true)),
|
||||
atom('on'),
|
||||
alt<any>(seq(map(group('(', bind(o as DataflowEndpointStatement, 'predicate',
|
||||
expr())),
|
||||
_ => o.triggerType = 'dataflow'),
|
||||
mandatoryIfNotTerminal(o, statement(o.body))),
|
||||
mapm(seq(bind(o, 'triggerType',
|
||||
alt(atomString('start'), atomString('stop'))),
|
||||
option(statement(o.body))),
|
||||
v => o.terminal ? fail : succeed(v)),
|
||||
seq(bind(o, 'triggerType',
|
||||
alt(atomString('asserted'),
|
||||
atomString('retracted'),
|
||||
atomString('message'))),
|
||||
option(map(atom(':snapshot'), _ => o.isDynamic = false)),
|
||||
bind(o as AssertionEventEndpointStatement, 'pattern',
|
||||
valuePattern(atom('=>'))),
|
||||
mandatoryIfNotTerminal(o, seq(atom('=>'), statement(o.body))))));
|
||||
});
|
||||
|
||||
export interface TypeDefinitionStatement {
|
||||
expectedUse: 'message' | 'assertion';
|
||||
label: Identifier;
|
||||
fields: Identifier[];
|
||||
fields: Binder[];
|
||||
wireName?: Expr;
|
||||
}
|
||||
|
||||
// Principal: none
|
||||
export const typeDefinitionStatement: Pattern<TypeDefinitionStatement> =
|
||||
scope(o => seq(bind(o, 'expectedUse', alt(atomString('message'), atomString('assertion'))),
|
||||
atom('type'),
|
||||
bind(o, 'label', identifier),
|
||||
group('(', bind(o, 'fields', repeat(identifier, { separator: atom(',') }))),
|
||||
option(seq(atom('='),
|
||||
bind(o, 'wireName', withoutSpace(upTo(statementBoundary))))),
|
||||
statementBoundary));
|
||||
|
||||
export interface MessageSendStatement extends FacetAction {
|
||||
expr: Expr;
|
||||
}
|
||||
|
||||
// Principal: Facet
|
||||
export const messageSendStatement: Pattern<MessageSendStatement> =
|
||||
facetAction(o => seq(atom('send'),
|
||||
atom('message'),
|
||||
not(statementBoundary),
|
||||
bind(o, 'expr', withoutSpace(upTo(statementBoundary))),
|
||||
statementBoundary));
|
||||
|
||||
export interface DuringStatement extends FacetAction {
|
||||
export interface DuringStatement extends FacetProducingAction {
|
||||
pattern: ValuePattern;
|
||||
body: Statement;
|
||||
}
|
||||
|
||||
// Principal: Facet
|
||||
export const duringStatement: Pattern<DuringStatement> =
|
||||
facetAction(o => {
|
||||
o.body = [];
|
||||
return seq(atom('during'),
|
||||
bind(o, 'pattern', valuePattern(atom('=>'))),
|
||||
seq(atom('=>'), statement(o.body)));
|
||||
});
|
||||
|
||||
// Principal: Facet
|
||||
export const reactStatement = blockFacetAction(atom('react'));
|
||||
|
||||
// Principal: none
|
||||
export const bootStatement: Pattern<Statement> =
|
||||
value(o => {
|
||||
o.value = [];
|
||||
return seq(atom('boot'), block(o.value));
|
||||
});
|
||||
|
||||
// Principal: Facet
|
||||
export const stopStatement = blockFacetAction(atom('stop'));
|
||||
export interface ReactStatement extends FacetProducingAction {
|
||||
}
|
||||
|
||||
export interface ActivationImport {
|
||||
activationKeyword: Identifier;
|
||||
target: { type: 'import', moduleName: Token } | { type: 'expr', moduleExpr: Expr };
|
||||
}
|
||||
|
||||
// Principal: none
|
||||
export const activationImport: Pattern<ActivationImport> =
|
||||
scope(o => seq(bind(o, 'activationKeyword', atom('activate')),
|
||||
follows(alt<any>(seq(atom('import'),
|
||||
upTo(seq(
|
||||
map(atom(void 0, { tokenType: TokenType.STRING }),
|
||||
n => o.target = { type: 'import', moduleName: n }),
|
||||
statementBoundary))),
|
||||
map(expr(), e => o.target = { type: 'expr', moduleExpr: e })))));
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Syntax of patterns over Value, used in endpoints
|
||||
// Value pattern AST types
|
||||
|
||||
export interface PCapture {
|
||||
type: 'PCapture',
|
||||
binder: Identifier,
|
||||
binder: Binder,
|
||||
inner: ValuePattern,
|
||||
}
|
||||
|
||||
|
@ -290,119 +130,335 @@ export interface PArray {
|
|||
|
||||
export type ValuePattern = PCapture | PDiscard | PConstructor | PConstant | PArray;
|
||||
|
||||
const pCaptureId: Pattern<Identifier> =
|
||||
mapm(identifier, i => i.text.startsWith('$')
|
||||
? succeed({ ... i, text: i.text.slice(1) })
|
||||
: fail);
|
||||
|
||||
const pDiscard: Pattern<void> = mapm(identifier, i => i.text === '_' ? succeed(void 0) : fail);
|
||||
|
||||
function hasCapturesOrDiscards(e: Expr): boolean {
|
||||
return foldItems(e,
|
||||
t => match(alt<any>(pCaptureId, pDiscard), [t], null) !== null,
|
||||
(_g, b, _k) => b,
|
||||
bs => bs.some(b => b));
|
||||
}
|
||||
|
||||
// $id - capture of discard
|
||||
// _ - discard
|
||||
//
|
||||
// expr(pat, ...) - record ctor
|
||||
// $id(pat) - nested capture
|
||||
// [pat, ...] - array pat
|
||||
//
|
||||
// expr(expr, ...) - constant
|
||||
// [expr, ...] - constant
|
||||
// other - constant
|
||||
|
||||
interface RawCall {
|
||||
items: Items;
|
||||
callee: Expr;
|
||||
arguments: Expr[];
|
||||
}
|
||||
|
||||
function pRawCall(... extraStops: Pattern<any>[]): Pattern<RawCall> {
|
||||
return scope((o: RawCall) => seq(bind(o, 'callee',
|
||||
expr(seq(group('(', discard),
|
||||
alt(exprBoundary, ... extraStops)))),
|
||||
seq(map(anything({ advance: false }),
|
||||
g => o.items = [... o.callee, g]),
|
||||
group('(', bind(o, 'arguments',
|
||||
separatedBy(expr(), atom(',')))))));
|
||||
}
|
||||
|
||||
function isConstant(o: RawCall) {
|
||||
return (!(hasCapturesOrDiscards(o.callee) || o.arguments.some(hasCapturesOrDiscards)));
|
||||
}
|
||||
|
||||
export function valuePattern(... extraStops: Pattern<any>[]): Pattern<ValuePattern> {
|
||||
return alt<ValuePattern>(
|
||||
scope<PCapture>(o => {
|
||||
o.type = 'PCapture';
|
||||
o.inner = { type: 'PDiscard' };
|
||||
return bind(o, 'binder', pCaptureId);
|
||||
}),
|
||||
scope(o => map(pDiscard, _ => o.type = 'PDiscard')),
|
||||
mapm<RawCall, ValuePattern>(
|
||||
pRawCall(... extraStops),
|
||||
o => {
|
||||
if (isConstant(o)) {
|
||||
return succeed({ type: 'PConstant', value: o.items });
|
||||
} else if (hasCapturesOrDiscards(o.callee)) {
|
||||
const r = match(pCaptureId, o.callee, null);
|
||||
if (r !== null && o.arguments.length === 1)
|
||||
{
|
||||
const argPat = match(valuePattern(), o.arguments[0], null);
|
||||
if (argPat === null) return fail;
|
||||
return succeed({
|
||||
type: 'PCapture',
|
||||
inner: argPat,
|
||||
binder: r
|
||||
});
|
||||
} else {
|
||||
return fail;
|
||||
}
|
||||
} else {
|
||||
const argPats = o.arguments.map(a => match(valuePattern(), a, null));
|
||||
if (argPats.some(p => p === null)) return fail;
|
||||
return succeed({
|
||||
type: 'PConstructor',
|
||||
ctor: o.callee,
|
||||
arguments: argPats as ValuePattern[]
|
||||
});
|
||||
}
|
||||
}),
|
||||
map(expr(), e => ({ type: 'PConstant', value: e }))
|
||||
);
|
||||
}
|
||||
|
||||
export function patternText(p: ValuePattern): Items {
|
||||
switch (p.type) {
|
||||
case 'PDiscard': return template`_`;
|
||||
case 'PConstant': return p.value;
|
||||
case 'PCapture':
|
||||
{
|
||||
const binder = { ... p.binder, text: '$' + p.binder.text };
|
||||
if (p.inner.type === 'PDiscard') {
|
||||
return [binder];
|
||||
} else {
|
||||
return template`${[binder]}(${patternText(p.inner)})`;
|
||||
}
|
||||
}
|
||||
case 'PArray': return template`[${commaJoin(p.elements.map(patternText))}]`;
|
||||
case 'PConstructor': return template`${p.ctor}(${commaJoin(p.arguments.map(patternText))})`;
|
||||
}
|
||||
}
|
||||
|
||||
export interface StaticAnalysis {
|
||||
skeleton: Expr;
|
||||
constPaths: Path[];
|
||||
constVals: Expr[];
|
||||
capturePaths: Path[];
|
||||
captureIds: Identifier[];
|
||||
captureBinders: Binder[];
|
||||
assertion: Expr;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Parsers
|
||||
|
||||
export class SyndicateParser {
|
||||
block(acc?: Items): Pattern<Items> {
|
||||
return group('{', map(rest, items => (acc?.push(... items), items)));
|
||||
}
|
||||
|
||||
readonly statementBoundary = alt<any>(atom(';'), Matcher.newline);
|
||||
readonly exprBoundary = alt<any>(atom(';'), atom(','), group('{', discard), Matcher.end);
|
||||
|
||||
readonly identifier: Pattern<Identifier> = atom();
|
||||
get binder(): Pattern<Binder> { return scope(o => bind(o, 'id', this.identifier)); }
|
||||
|
||||
expr(... extraStops: Pattern<any>[]): Pattern<Expr> {
|
||||
return withoutSpace(upTo(alt(this.exprBoundary, ... extraStops)));
|
||||
}
|
||||
|
||||
readonly type: (... extraStops: Pattern<any>[]) => Pattern<Type> = this.expr;
|
||||
|
||||
statement(acc: Items): Pattern<any> {
|
||||
return alt<any>(this.block(acc),
|
||||
withoutSpace(seq(map(upTo(this.statementBoundary),
|
||||
items => acc.push(... items)),
|
||||
map(this.statementBoundary,
|
||||
i => i ? acc.push(i) : void 0))));
|
||||
}
|
||||
|
||||
facetAction<T extends FacetAction>(pattern: (scope: T) => Pattern<any>): Pattern<T> {
|
||||
return i => {
|
||||
const scope = Object.create(null);
|
||||
scope.implicitFacet = true;
|
||||
const p = seq(option(map(atom('.'), _ => scope.implicitFacet = false)), pattern(scope));
|
||||
const r = p(i);
|
||||
if (r === null) return null;
|
||||
return [scope, r[1]];
|
||||
};
|
||||
}
|
||||
|
||||
readonly headerExpr = this.expr(atom(':asserting'), atom(':let'));
|
||||
|
||||
// Principal: Facet
|
||||
readonly spawn: Pattern<SpawnStatement> =
|
||||
this.facetAction(o => {
|
||||
o.isDataspace = false;
|
||||
o.initialAssertions = [];
|
||||
o.parentBinders = [];
|
||||
o.parentInits = [];
|
||||
o.body = [];
|
||||
o.facetFields = [];
|
||||
return seq(atom('spawn'),
|
||||
option(seq(atom('dataspace'), exec(() => o.isDataspace = true))),
|
||||
option(seq(atom('named'),
|
||||
bind(o, 'name', this.headerExpr))),
|
||||
repeat(alt(seq(atom(':asserting'),
|
||||
map(this.headerExpr, e => o.initialAssertions.push(e))),
|
||||
map(scope((l: { b: Binder, init: Expr }) =>
|
||||
seq(atom(':let'),
|
||||
bind(l, 'b', this.binder),
|
||||
atom('='),
|
||||
bind(l, 'init', this.headerExpr))),
|
||||
l => {
|
||||
o.parentBinders.push(l.b);
|
||||
o.parentInits.push(l.init);
|
||||
}))),
|
||||
this.block(o.body));
|
||||
});
|
||||
|
||||
// Principal: Dataspace, but only for implementation reasons, so really Facet
|
||||
readonly fieldDeclarationStatement: Pattern<FieldDeclarationStatement> =
|
||||
this.facetAction(o => {
|
||||
return seq(atom('field'),
|
||||
bind(o, 'property', this.binder),
|
||||
option(seq(atom('='), bind(o, 'init', this.expr()))),
|
||||
this.statementBoundary);
|
||||
});
|
||||
|
||||
// Principal: Facet
|
||||
readonly assertionEndpointStatement: Pattern<AssertionEndpointStatement> =
|
||||
this.facetAction(o => {
|
||||
o.isDynamic = true;
|
||||
return seq(atom('assert'),
|
||||
option(map(atom(':snapshot'), _ => o.isDynamic = false)),
|
||||
bind(o, 'template', this.expr(seq(atom('when'), group('(', discard)))),
|
||||
option(seq(atom('when'), group('(', bind(o, 'test', this.expr())))),
|
||||
this.statementBoundary);
|
||||
});
|
||||
|
||||
blockFacetAction(kw: Pattern<any>): Pattern<StatementFacetAction> {
|
||||
return this.facetAction(o => {
|
||||
o.body = [];
|
||||
return seq(kw, this.block(o.body));
|
||||
});
|
||||
}
|
||||
|
||||
// Principal: Facet
|
||||
readonly dataflowStatement = this.blockFacetAction(atom('dataflow'));
|
||||
|
||||
mandatoryIfNotTerminal(o: GenericEventEndpointStatement, p: Pattern<any>): Pattern<any> {
|
||||
return i => {
|
||||
return (o.terminal) ? option(p)(i) : p(i);
|
||||
};
|
||||
}
|
||||
|
||||
// Principal: Facet
|
||||
readonly eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> =
|
||||
this.facetAction(o => {
|
||||
o.terminal = false;
|
||||
o.isDynamic = true;
|
||||
o.body = [];
|
||||
return seq(option(map(atom('stop'), _ => o.terminal = true)),
|
||||
atom('on'),
|
||||
alt<any>(seq(map(group('(', bind(o as DataflowEndpointStatement, 'predicate',
|
||||
this.expr())),
|
||||
_ => o.triggerType = 'dataflow'),
|
||||
this.mandatoryIfNotTerminal(o, this.statement(o.body))),
|
||||
mapm(seq(bind(o, 'triggerType',
|
||||
alt(atomString('start'), atomString('stop'))),
|
||||
option(this.statement(o.body))),
|
||||
v => o.terminal ? fail : succeed(v)),
|
||||
seq(bind(o, 'triggerType',
|
||||
alt(atomString('asserted'),
|
||||
atomString('retracted'),
|
||||
atomString('message'))),
|
||||
option(map(atom(':snapshot'), _ => o.isDynamic = false)),
|
||||
bind(o as AssertionEventEndpointStatement, 'pattern',
|
||||
this.valuePattern(atom('=>'))),
|
||||
this.mandatoryIfNotTerminal(
|
||||
o, seq(atom('=>'), this.statement(o.body))))));
|
||||
});
|
||||
|
||||
// Principal: none
|
||||
readonly typeDefinitionStatement: Pattern<TypeDefinitionStatement> =
|
||||
scope(o => seq(bind(o, 'expectedUse', alt(atomString('message'), atomString('assertion'))),
|
||||
atom('type'),
|
||||
bind(o, 'label', this.identifier),
|
||||
group('(', bind(o, 'fields',
|
||||
repeat(this.binder, { separator: atom(',') }))),
|
||||
option(seq(atom('='),
|
||||
bind(o, 'wireName', withoutSpace(upTo(this.statementBoundary))))),
|
||||
this.statementBoundary));
|
||||
|
||||
// Principal: Facet
|
||||
readonly messageSendStatement: Pattern<MessageSendStatement> =
|
||||
this.facetAction(o => seq(atom('send'),
|
||||
atom('message'),
|
||||
not(this.statementBoundary),
|
||||
bind(o, 'expr', withoutSpace(upTo(this.statementBoundary))),
|
||||
this.statementBoundary));
|
||||
|
||||
// Principal: Facet
|
||||
readonly duringStatement: Pattern<DuringStatement> =
|
||||
this.facetAction(o => {
|
||||
o.body = [];
|
||||
o.facetFields = [];
|
||||
return seq(atom('during'),
|
||||
bind(o, 'pattern', this.valuePattern(atom('=>'))),
|
||||
seq(atom('=>'), this.statement(o.body)));
|
||||
});
|
||||
|
||||
// Principal: Facet
|
||||
readonly reactStatement: Pattern<ReactStatement> =
|
||||
this.facetAction(o => {
|
||||
o.body = [];
|
||||
o.facetFields = [];
|
||||
return seq(atom('react'), this.block(o.body));
|
||||
});
|
||||
|
||||
// Principal: none
|
||||
readonly bootStatement: Pattern<Statement> =
|
||||
value(o => {
|
||||
o.value = [];
|
||||
return seq(atom('boot'), this.block(o.value));
|
||||
});
|
||||
|
||||
// Principal: Facet
|
||||
readonly stopStatement = this.blockFacetAction(atom('stop'));
|
||||
|
||||
// Principal: none
|
||||
readonly activationImport: Pattern<ActivationImport> =
|
||||
scope(o => seq(bind(o, 'activationKeyword', atom('activate')),
|
||||
follows(alt<any>(seq(atom('import'),
|
||||
upTo(seq(
|
||||
map(atom(void 0, { tokenType: TokenType.STRING }),
|
||||
n => o.target = {
|
||||
type: 'import',
|
||||
moduleName: n
|
||||
}),
|
||||
this.statementBoundary))),
|
||||
map(this.expr(), e => o.target = {
|
||||
type: 'expr',
|
||||
moduleExpr: e
|
||||
})))));
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Syntax of patterns over Value, used in endpoints
|
||||
|
||||
readonly pCaptureBinder: Pattern<Binder> =
|
||||
mapm(this.binder, i => {
|
||||
return i.id.text.startsWith('$')
|
||||
? succeed({ id: { ... i.id, text: i.id.text.slice(1) }, type: i.type })
|
||||
: fail;
|
||||
});
|
||||
|
||||
readonly pDiscard: Pattern<void> =
|
||||
mapm(this.identifier, i => i.text === '_' ? succeed(void 0) : fail);
|
||||
|
||||
hasCapturesOrDiscards(e: Expr): boolean {
|
||||
return foldItems(e,
|
||||
t => match(alt<any>(this.pCaptureBinder, this.pDiscard), [t], null) !== null,
|
||||
(_g, b, _k) => b,
|
||||
bs => bs.some(b => b));
|
||||
}
|
||||
|
||||
// $id - capture of discard
|
||||
// _ - discard
|
||||
//
|
||||
// expr(pat, ...) - record ctor
|
||||
// $id(pat) - nested capture
|
||||
// [pat, ...] - array pat
|
||||
//
|
||||
// expr(expr, ...) - constant
|
||||
// [expr, ...] - constant
|
||||
// other - constant
|
||||
|
||||
pRawCall(... extraStops: Pattern<any>[]): Pattern<RawCall> {
|
||||
return scope((o: RawCall) =>
|
||||
seq(bind(o, 'callee',
|
||||
this.expr(seq(group('(', discard),
|
||||
alt(this.exprBoundary, ... extraStops)))),
|
||||
seq(map(anything({ advance: false }),
|
||||
g => o.items = [... o.callee, g]),
|
||||
group('(', bind(o, 'arguments',
|
||||
separatedBy(this.expr(), atom(',')))))));
|
||||
}
|
||||
|
||||
isConstant(o: RawCall): boolean {
|
||||
return (!(this.hasCapturesOrDiscards(o.callee) ||
|
||||
o.arguments.some(a => this.hasCapturesOrDiscards(a))));
|
||||
}
|
||||
|
||||
valuePattern(... extraStops: Pattern<any>[]): Pattern<ValuePattern> {
|
||||
return alt<ValuePattern>(
|
||||
scope<PCapture>(o => {
|
||||
o.type = 'PCapture';
|
||||
o.inner = { type: 'PDiscard' };
|
||||
return bind(o, 'binder', this.pCaptureBinder);
|
||||
}),
|
||||
scope(o => map(this.pDiscard, _ => o.type = 'PDiscard')),
|
||||
mapm<RawCall, ValuePattern>(
|
||||
this.pRawCall(... extraStops),
|
||||
o => {
|
||||
if (this.isConstant(o)) {
|
||||
return succeed({ type: 'PConstant', value: o.items });
|
||||
} else if (this.hasCapturesOrDiscards(o.callee)) {
|
||||
const r = match(this.pCaptureBinder, o.callee, null);
|
||||
if (r !== null && o.arguments.length === 1)
|
||||
{
|
||||
const argPat = match(this.valuePattern(), o.arguments[0], null);
|
||||
if (argPat === null) return fail;
|
||||
return succeed({
|
||||
type: 'PCapture',
|
||||
inner: argPat,
|
||||
binder: r
|
||||
});
|
||||
} else {
|
||||
return fail;
|
||||
}
|
||||
} else {
|
||||
const argPats = o.arguments.map(a => match(this.valuePattern(), a, null));
|
||||
if (argPats.some(p => p === null)) return fail;
|
||||
return succeed({
|
||||
type: 'PConstructor',
|
||||
ctor: o.callee,
|
||||
arguments: argPats as ValuePattern[]
|
||||
});
|
||||
}
|
||||
}),
|
||||
map(this.expr(), e => ({ type: 'PConstant', value: e }))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class SyndicateTypedParser extends SyndicateParser {
|
||||
get binder(): Pattern<Binder> {
|
||||
return scope(o => seq(bind(o, 'id', this.identifier),
|
||||
option(seq(atom(':'),
|
||||
bind(o, 'type', this.type(atom('=')))))));
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Value pattern utilities
|
||||
|
||||
export function patternText(p: ValuePattern): Items {
|
||||
switch (p.type) {
|
||||
case 'PDiscard': return template`_`;
|
||||
case 'PConstant': return p.value;
|
||||
case 'PCapture':
|
||||
{
|
||||
const binderId = { ... p.binder.id, text: '$' + p.binder.id.text };
|
||||
const affix =
|
||||
(p.inner.type === 'PDiscard') ? [] : template`(${patternText(p.inner)})`;
|
||||
if (p.binder.type !== void 0) {
|
||||
return template`${[binderId]}:${p.binder.type}${affix}`;
|
||||
} else {
|
||||
return template`${[binderId]}${affix}`;
|
||||
}
|
||||
}
|
||||
case 'PArray': return template`[${commaJoin(p.elements.map(patternText))}]`;
|
||||
case 'PConstructor': return template`${p.ctor}(${commaJoin(p.arguments.map(patternText))})`;
|
||||
}
|
||||
}
|
||||
|
||||
const eDiscard: Expr = template`(__SYNDICATE__.Discard._instance)`;
|
||||
const eCapture = (e: Expr): Expr => template`(__SYNDICATE__.Capture(${e}))`;
|
||||
|
||||
|
@ -410,7 +466,7 @@ export function compilePattern(pattern: ValuePattern): StaticAnalysis {
|
|||
const constPaths: Path[] = [];
|
||||
const constVals: Expr[] = [];
|
||||
const capturePaths: Path[] = [];
|
||||
const captureIds: Identifier[] = [];
|
||||
const captureBinders: Binder[] = [];
|
||||
|
||||
const currentPath: Path = [];
|
||||
|
||||
|
@ -420,7 +476,7 @@ export function compilePattern(pattern: ValuePattern): StaticAnalysis {
|
|||
return [null, eDiscard];
|
||||
case 'PCapture': {
|
||||
capturePaths.push(currentPath.slice());
|
||||
captureIds.push(pattern.binder);
|
||||
captureBinders.push(pattern.binder);
|
||||
const [s, a] = walk(pattern.inner);
|
||||
return [s, eCapture(a)];
|
||||
}
|
||||
|
@ -473,7 +529,7 @@ export function compilePattern(pattern: ValuePattern): StaticAnalysis {
|
|||
constPaths,
|
||||
constVals,
|
||||
capturePaths,
|
||||
captureIds,
|
||||
captureBinders,
|
||||
assertion,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -130,8 +130,9 @@ export abstract class Scanner implements IterableIterator<Token> {
|
|||
buf = buf + this.shiftChar();
|
||||
while (true) {
|
||||
ch = this.shiftChar();
|
||||
if ((ch === null) ||((ch === '/') && seenStar)) break;
|
||||
if (ch === null) break;
|
||||
buf = buf + ch;
|
||||
if ((ch === '/') && seenStar) break;
|
||||
seenStar = (ch === '*');
|
||||
}
|
||||
return this._collectSpace(buf, start);
|
||||
|
@ -168,16 +169,15 @@ export abstract class Scanner implements IterableIterator<Token> {
|
|||
case '`':
|
||||
return this._str(false);
|
||||
|
||||
case '.':
|
||||
case ',':
|
||||
case ';':
|
||||
return this._punct(TokenType.ATOM);
|
||||
|
||||
case '/':
|
||||
return this._maybeComment();
|
||||
|
||||
default:
|
||||
return this._atom(this.mark(), this.shiftChar()!);
|
||||
if (this.isDelimiter(ch)) {
|
||||
return this._punct(TokenType.ATOM);
|
||||
} else {
|
||||
return this._atom(this.mark(), this.shiftChar()!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,2 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
require('../lib/tsc.js').main(process.argv.slice(2));
|
||||
try {
|
||||
require('../lib/tsc.js').main(process.argv.slice(2));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
@ -1,2 +1,8 @@
|
|||
#!/usr/bin/env node
|
||||
require('../lib/cli.js').main(process.argv.slice(2));
|
||||
try {
|
||||
require('../lib/cli.js').main(process.argv.slice(2));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import { BoxState, SetBox, N } from './protocol.js';
|
|||
|
||||
boot {
|
||||
spawn named 'box' {
|
||||
field this.value = 0;
|
||||
field value = 0;
|
||||
assert BoxState(this.value);
|
||||
stop on (this.value === N)
|
||||
console.log('terminated box root facet');
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"main": "index.js",
|
||||
"scripts": {
|
||||
"prepare": "npm run compile && npm run rollup",
|
||||
"compile": "npx syndicatec -d lib -b src 'src/**/*.js'",
|
||||
"compile": "../../bin/syndicate-tsc.js",
|
||||
"rollup": "npx rollup -c",
|
||||
"clean": "rm -rf lib/ index.js index.js.map"
|
||||
},
|
||||
|
|
|
@ -20,7 +20,7 @@ import { BoxState, SetBox, N } from './protocol.js';
|
|||
|
||||
boot {
|
||||
spawn named 'box' {
|
||||
field this.value: number = 0;
|
||||
field value: number = 0;
|
||||
assert BoxState(this.value);
|
||||
stop on (this.value === N)
|
||||
console.log('terminated box root facet');
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"declarationDir": "./lib",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"module": "es6",
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@ import path from 'path';
|
|||
import { glob } from 'glob';
|
||||
|
||||
import { compile } from '@syndicate-lang/compiler';
|
||||
import { dataURL, sourceMappingComment} from './util.js';
|
||||
|
||||
export type ModuleChoice = 'es6' | 'require' | 'global';
|
||||
const moduleChoices: ReadonlyArray<ModuleChoice> = ['es6', 'require', 'global'];
|
||||
|
@ -18,6 +19,7 @@ export type CommandLineArguments = {
|
|||
mapExtension?: string;
|
||||
runtime: string;
|
||||
module: ModuleChoice;
|
||||
typed: boolean;
|
||||
}
|
||||
|
||||
function checkModuleChoice<T>(t: T & { module: string }): T & { module: ModuleChoice } {
|
||||
|
@ -97,6 +99,12 @@ export function main(argv: string[]) {
|
|||
description: 'Path to require or import to get the Syndicate runtime',
|
||||
default: '@syndicate-lang/core',
|
||||
})
|
||||
.option('typed', {
|
||||
alias: 't',
|
||||
type: 'boolean',
|
||||
description: 'Enable TypeScript-typed translation',
|
||||
default: false,
|
||||
})
|
||||
.option('module', {
|
||||
choices: moduleChoices,
|
||||
type: 'string',
|
||||
|
@ -132,14 +140,10 @@ export function main(argv: string[]) {
|
|||
name: inputFilename,
|
||||
runtime: options.runtime,
|
||||
module: options.module,
|
||||
typescript: options.typed,
|
||||
});
|
||||
map.sourcesContent = [source];
|
||||
|
||||
function mapDataURL() {
|
||||
const mapData = Buffer.from(JSON.stringify(map)).toString('base64')
|
||||
return `data:application/json;base64,${mapData}`;
|
||||
}
|
||||
|
||||
if (inputFilename !== STDIN) {
|
||||
fs.mkdirSync(path.dirname(outputFilename), { recursive: true });
|
||||
}
|
||||
|
@ -148,10 +152,10 @@ export function main(argv: string[]) {
|
|||
fs.writeFileSync(outputFilename, text);
|
||||
} else if (options.mapExtension && inputFilename !== STDIN) {
|
||||
const mapFilename = outputFilename + options.mapExtension;
|
||||
fs.writeFileSync(outputFilename, text + `\n//# sourceMappingURL=${mapFilename}`);
|
||||
fs.writeFileSync(outputFilename, text + sourceMappingComment(mapFilename));
|
||||
fs.writeFileSync(mapFilename, JSON.stringify(map));
|
||||
} else {
|
||||
fs.writeFileSync(outputFilename, text + `\n//# sourceMappingURL=${mapDataURL()}`);
|
||||
fs.writeFileSync(outputFilename, text + sourceMappingComment(dataURL(JSON.stringify(map))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import ts from 'typescript';
|
|||
import crypto from 'crypto';
|
||||
|
||||
import { compile } from '@syndicate-lang/compiler';
|
||||
import { dataURL, sourceMappingComment } from './util.js';
|
||||
|
||||
function reportDiagnostic(diagnostic: ts.Diagnostic) {
|
||||
if (diagnostic.file) {
|
||||
|
@ -49,15 +50,18 @@ function createProgram(rootNames: readonly string[] | undefined,
|
|||
onError?.(`Could not read input file ${fileName}`);
|
||||
return undefined;
|
||||
}
|
||||
const expandedText = compile({
|
||||
const { text: baseExpandedText, map: sourceMap } = compile({
|
||||
source: inputText,
|
||||
name: fileName,
|
||||
typescript: true,
|
||||
}).text;
|
||||
console.log('\n\n', fileName);
|
||||
expandedText.split(/\n/).forEach((line, i) => {
|
||||
console.log(i, line);
|
||||
});
|
||||
sourceMap.sourcesContent = [inputText];
|
||||
const expandedText = baseExpandedText + sourceMappingComment(dataURL(JSON.stringify(sourceMap)));
|
||||
|
||||
// console.log('\n\n', fileName);
|
||||
// expandedText.split(/\n/).forEach((line, i) => {
|
||||
// console.log(i + 1, line);
|
||||
// });
|
||||
const sf = ts.createSourceFile(fileName, expandedText, languageVersion, true);
|
||||
(sf as any).version = crypto.createHash('sha256').update(expandedText).digest('hex');
|
||||
return sf;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export function dataURL(s: string): string {
|
||||
return `data:application/json;base64,${Buffer.from(s).toString('base64')}`;
|
||||
}
|
||||
|
||||
export function sourceMappingComment(url: string): string {
|
||||
return `\n//# sourceMappingURL=${url}`;
|
||||
}
|
Loading…
Reference in New Issue