Skip to content

Commit

Permalink
Readonly scope (#649)
Browse files Browse the repository at this point in the history
* WIP

* WIP

* Fixes

* Fixed type errors

* Forgot one character

* Trigger CI
  • Loading branch information
RunDevelopment authored Jul 29, 2022
1 parent 63facaa commit 0cad442
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 139 deletions.
10 changes: 5 additions & 5 deletions src/common/types/chainner-scope.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { lazy } from '../util';
import { globalScope } from './global-scope';
import { parseDefinitions } from './parse';
import { ReadonlyScope, Scope } from './scope';
import { Scope, ScopeBuilder } from './scope';
import { SourceDocument } from './source';

const code = `
Expand Down Expand Up @@ -80,13 +80,13 @@ def getUpscaleChannels(
}
`;

export const getChainnerScope = lazy((): ReadonlyScope => {
const scope = new Scope('Chainner scope', globalScope);
export const getChainnerScope = lazy((): Scope => {
const builder = new ScopeBuilder('Chainner scope', globalScope);

const definitions = parseDefinitions(new SourceDocument(code, 'chainner-internal'));
for (const d of definitions) {
scope.add(d);
builder.add(d);
}

return scope;
return builder.createScope();
});
49 changes: 26 additions & 23 deletions src/common/types/evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ import { isSubsetOf } from './relation';
import {
BuiltinFunctionDefinition,
NameResolutionError,
ReadonlyScope,
ResolvedName,
Scope,
ScopeBuilder,
ScopeBuiltinFunctionDefinition,
ScopeFunctionDefinition,
ScopeParameterDefinition,
ScopeStructDefinition,
ScopeVariableDefinition,
} from './scope';
Expand Down Expand Up @@ -122,10 +123,7 @@ export class EvaluationError extends Error {
}
}

const evaluateStructDefinition = (
def: StructDefinition,
scope: ReadonlyScope
): StructType | NeverType => {
const evaluateStructDefinition = (def: StructDefinition, scope: Scope): StructType | NeverType => {
const fields: StructTypeField[] = [];
for (const f of def.fields) {
let type;
Expand All @@ -149,9 +147,9 @@ const evaluateStructDefinition = (
};
const evaluateStruct = (
expression: NamedExpression,
scope: ReadonlyScope,
scope: Scope,
definition: ScopeStructDefinition,
definitionScope: ReadonlyScope
definitionScope: Scope
): Type => {
// eslint-disable-next-line no-param-reassign
definition.default ??= evaluateStructDefinition(definition.definition, definitionScope);
Expand Down Expand Up @@ -208,8 +206,8 @@ const evaluateStruct = (

const resolveNamed = (
expression: NamedExpression,
currentScope: ReadonlyScope
): ResolvedName<ScopeStructDefinition | ScopeVariableDefinition> => {
currentScope: Scope
): ResolvedName<ScopeStructDefinition | ScopeVariableDefinition | ScopeParameterDefinition> => {
let resolved;
try {
resolved = currentScope.get(expression.name);
Expand All @@ -236,9 +234,14 @@ const resolveNamed = (

return { definition, scope };
};
const evaluateNamed = (expression: NamedExpression, scope: ReadonlyScope): Type => {
const evaluateNamed = (expression: NamedExpression, scope: Scope): Type => {
const { definition, scope: definitionScope } = resolveNamed(expression, scope);

// parameter
if (definition.type === 'parameter') {
return definition.value;
}

// variable
if (definition.type === 'variable') {
if (expression.fields.length > 0) {
Expand All @@ -260,7 +263,7 @@ const evaluateNamed = (expression: NamedExpression, scope: ReadonlyScope): Type
return evaluateStruct(expression, scope, definition, definitionScope);
};

const evaluateFieldAccess = (expression: FieldAccessExpression, scope: ReadonlyScope): Type => {
const evaluateFieldAccess = (expression: FieldAccessExpression, scope: Scope): Type => {
const type = evaluate(expression.of, scope);
if (type.type === 'never') return NeverType.instance;
if (type.type === 'any') {
Expand Down Expand Up @@ -304,7 +307,7 @@ const evaluateFieldAccess = (expression: FieldAccessExpression, scope: ReadonlyS

const resolveFunction = (
expression: FunctionCallExpression,
currentScope: ReadonlyScope
currentScope: Scope
): ResolvedName<ScopeFunctionDefinition | ScopeBuiltinFunctionDefinition> => {
let resolved;
try {
Expand Down Expand Up @@ -332,7 +335,7 @@ const resolveFunction = (
message: `The name ${expression.functionName} resolves to a ${resolved.definition.type} and not a function.`,
});
};
const evaluateFunctionCall = (expression: FunctionCallExpression, scope: ReadonlyScope): Type => {
const evaluateFunctionCall = (expression: FunctionCallExpression, scope: Scope): Type => {
const { definition, scope: definitionScope } = resolveFunction(expression, scope);

// check argument number
Expand Down Expand Up @@ -411,25 +414,25 @@ const evaluateFunctionCall = (expression: FunctionCallExpression, scope: Readonl

// run function
if (definition.type === 'function') {
const functionScope = new Scope('function scope', definitionScope);
const functionScope = new ScopeBuilder('function scope', definitionScope);
definition.definition.parameters.forEach(({ name }, i) => {
functionScope.add(new VariableDefinition(name, args[i]));
});
return evaluate(definition.definition.value, functionScope);
return evaluate(definition.definition.value, functionScope.createScope());
}
return definition.definition.fn(...args);
};

const evaluateMatch = (expression: MatchExpression, scope: ReadonlyScope): Type => {
const evaluateMatch = (expression: MatchExpression, scope: Scope): Type => {
let type = evaluate(expression.of, scope);
if (type.type === 'never') return NeverType.instance;

const withBinding = (arm: MatchArm, armType: Type): ReadonlyScope => {
const withBinding = (arm: MatchArm, armType: Type): Scope => {
if (arm.binding === undefined) return scope;

const armScope = new Scope(`match arm`, scope);
const armScope = new ScopeBuilder(`match arm`, scope);
armScope.add(new VariableDefinition(arm.binding, armType));
return armScope;
return armScope.createScope();
};

const matchTypes: Type[] = [];
Expand All @@ -445,27 +448,27 @@ const evaluateMatch = (expression: MatchExpression, scope: ReadonlyScope): Type
return union(...matchTypes);
};

const evaluateScope = (expression: ScopeExpression, parentScope: ReadonlyScope): Type => {
const evaluateScope = (expression: ScopeExpression, parentScope: Scope): Type => {
let name = 'scope expression';
if (expression.source) {
const { document, span } = expression.source;
name += ` at ${document.name}:${span[0]}`;
}
const scope = new Scope(name, parentScope);

const scope = new ScopeBuilder(name, parentScope);
for (const def of expression.definitions) {
scope.add(def);
}

return evaluate(expression.expression, scope);
return evaluate(expression.expression, scope.createScope());
};

/**
* Evaluates the given expression. If a type is given, then the type will be returned as is.
*
* @throws {@link EvaluationError}
*/
export const evaluate = (expression: Expression, scope: ReadonlyScope): Type => {
export const evaluate = (expression: Expression, scope: Scope): Type => {
if (expression.underlying !== 'expression') {
// type
return expression;
Expand Down
65 changes: 42 additions & 23 deletions src/common/types/function.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Input, InputId, InputSchemaValue, NodeSchema, Output, OutputId } from '../common-types';
import { EMPTY_MAP, topologicalSort } from '../util';
import { EMPTY_MAP, lazy, topologicalSort } from '../util';
import { getChainnerScope } from './chainner-scope';
import { evaluate } from './evaluate';
import { Expression, VariableDefinition } from './expression';
import { Expression } from './expression';
import { intersect, isDisjointWith } from './intersection';
import { fromJson } from './json';
import { ReadonlyScope, Scope } from './scope';
import { AnyType, NonNeverType, Type } from './types';
import { ParameterDefinition, Scope, ScopeBuilder } from './scope';
import { NonNeverType, Type } from './types';
import { getReferences } from './util';

const getConversionScope = lazy(() => {
const scope = new ScopeBuilder('Conversion scope', getChainnerScope());
scope.add(new ParameterDefinition('Input'));
return scope.createScope();
});

type IdType<P extends 'Input' | 'Output'> = P extends 'Input' ? InputId : OutputId;
const getParamRefs = <P extends 'Input' | 'Output'>(
expression: Expression,
Expand Down Expand Up @@ -40,7 +46,7 @@ interface InputInfo {
}
const evaluateInputs = (
schema: NodeSchema,
scope: ReadonlyScope
scope: Scope
): { ordered: InputInfo[]; defaults: Map<InputId, NonNeverType> } => {
const inputIds = new Set(schema.inputs.map((i) => i.id));

Expand All @@ -65,7 +71,11 @@ const evaluateInputs = (
}
ordered.reverse();

const expressionScope = new Scope('evaluateInputs scope', scope);
const expressionScopeBuilder = new ScopeBuilder('evaluateInputs scope', scope);
for (const inputId of inputIds) {
expressionScopeBuilder.add(new ParameterDefinition(getInputParamName(inputId)));
}
const expressionScope = expressionScopeBuilder.createScope();

const defaults = new Map<InputId, NonNeverType>();
for (const { expression, input } of ordered) {
Expand All @@ -82,7 +92,7 @@ const evaluateInputs = (
}

defaults.set(input.id, type);
expressionScope.add(new VariableDefinition(getInputParamName(input.id), type));
expressionScope.assignParameter(getInputParamName(input.id), type);
}

return { ordered, defaults };
Expand All @@ -96,7 +106,7 @@ interface OutputInfo {
}
const evaluateOutputs = (
schema: NodeSchema,
scope: ReadonlyScope,
scope: Scope,
inputDefaults: ReadonlyMap<InputId, NonNeverType>
): { ordered: OutputInfo[]; defaults: Map<OutputId, NonNeverType> } => {
const inputIds = new Set(inputDefaults.keys());
Expand Down Expand Up @@ -127,10 +137,14 @@ const evaluateOutputs = (
}
ordered.reverse();

const expressionScope = new Scope('evaluateOutputs scope', scope);
const expressionScopeBuilder = new ScopeBuilder('evaluateOutputs scope', scope);
for (const [inputId, inputType] of inputDefaults) {
expressionScope.add(new VariableDefinition(getInputParamName(inputId), inputType));
expressionScopeBuilder.add(new ParameterDefinition(getInputParamName(inputId), inputType));
}
for (const outputId of outputIds) {
expressionScopeBuilder.add(new ParameterDefinition(getOutputParamName(outputId)));
}
const expressionScope = expressionScopeBuilder.createScope();

const defaults = new Map<OutputId, NonNeverType>();
for (const { expression, output } of ordered) {
Expand All @@ -147,14 +161,14 @@ const evaluateOutputs = (
}

defaults.set(output.id, type);
expressionScope.add(new VariableDefinition(getOutputParamName(output.id), type));
expressionScope.assignParameter(getOutputParamName(output.id), type);
}
return { ordered, defaults };
};

const evaluateInputOptions = (
schema: NodeSchema,
scope: ReadonlyScope
scope: Scope
): Map<InputId, Map<InputSchemaValue, NonNeverType>> => {
const result = new Map<InputId, Map<InputSchemaValue, NonNeverType>>();
for (const input of schema.inputs) {
Expand Down Expand Up @@ -196,10 +210,8 @@ const getConversions = (schema: NodeSchema): Map<InputId, Expression> => {
const e = fromJson(input.conversion);

// verify that it's a valid conversion
const scope = new Scope('test scope', getChainnerScope());
scope.add(new VariableDefinition('Input', AnyType.instance));
try {
evaluate(e, scope);
evaluate(e, getConversionScope());
} catch (error) {
const name = `${schema.name} (id: ${schema.schemaId}) > ${input.label} (id: ${input.id})`;
throw new Error(`The conversion of input ${name} is invalid: ${String(error)}`);
Expand All @@ -213,7 +225,7 @@ const getConversions = (schema: NodeSchema): Map<InputId, Expression> => {
export class FunctionDefinition {
readonly schema: NodeSchema;

readonly scope: ReadonlyScope;
readonly scope: Scope;

readonly inputDefaults: ReadonlyMap<InputId, NonNeverType>;

Expand Down Expand Up @@ -245,7 +257,7 @@ export class FunctionDefinition {

readonly defaultInstance: FunctionInstance;

private constructor(schema: NodeSchema, scope: ReadonlyScope) {
private constructor(schema: NodeSchema, scope: Scope) {
this.schema = schema;
this.scope = scope;

Expand Down Expand Up @@ -294,7 +306,7 @@ export class FunctionDefinition {
this.defaultInstance = FunctionInstance.fromDefinition(this);
}

static fromSchema(schema: NodeSchema, scope: ReadonlyScope): FunctionDefinition {
static fromSchema(schema: NodeSchema, scope: Scope): FunctionDefinition {
return new FunctionDefinition(schema, scope);
}

Expand All @@ -304,8 +316,8 @@ export class FunctionDefinition {
return type;
}

const scope = new Scope('Conversion scope', getChainnerScope());
scope.add(new VariableDefinition('Input', type));
const scope = getConversionScope();
scope.assignParameter('Input', type);
return evaluate(conversion, scope);
}

Expand Down Expand Up @@ -387,7 +399,14 @@ export class FunctionInstance {
const outputErrors: FunctionOutputError[] = [];

// scope
const scope = new Scope('function instance', definition.scope);
const scopeBuilder = new ScopeBuilder('function instance', definition.scope);
for (const [inputId, type] of definition.inputDefaults) {
scopeBuilder.add(new ParameterDefinition(getInputParamName(inputId), type));
}
for (const [outputId, type] of definition.outputDefaults) {
scopeBuilder.add(new ParameterDefinition(getOutputParamName(outputId), type));
}
const scope = scopeBuilder.createScope();

// evaluate inputs
const inputs = new Map<InputId, NonNeverType>();
Expand Down Expand Up @@ -419,7 +438,7 @@ export class FunctionInstance {
}

inputs.set(id, type);
scope.add(new VariableDefinition(getInputParamName(id), type));
scope.assignParameter(getInputParamName(id), type);
}

// we don't need to evaluate the outputs of if they aren't generic
Expand Down Expand Up @@ -459,7 +478,7 @@ export class FunctionInstance {
}

outputs.set(id, type);
scope.add(new VariableDefinition(getOutputParamName(id), type));
scope.assignParameter(getOutputParamName(id), type);
}

return new FunctionInstance(definition, inputs, outputs, inputErrors, outputErrors);
Expand Down
Loading

0 comments on commit 0cad442

Please sign in to comment.