Skip to content

Commit

Permalink
Refactor validator (#1583)
Browse files Browse the repository at this point in the history
* moved validation function to validator

* update validator imports

* linting

* format

* re-format with new version

* fixing merge conflict fixes

* linting

---------

Co-authored-by: Martin Henz <[email protected]>
  • Loading branch information
hanscau and martin-henz authored Mar 24, 2024
1 parent 732ef26 commit caf8c3a
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 236 deletions.
2 changes: 1 addition & 1 deletion src/cse-machine/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ import { UndefinedImportError } from '../modules/errors'
import { initModuleContext, loadModuleBundle } from '../modules/moduleLoader'
import { ImportTransformOptions } from '../modules/moduleTypes'
import { checkEditorBreakpoints } from '../stdlib/inspector'
import { checkProgramForUndefinedVariables } from '../transpiler/transpiler'
import { Context, ContiguousArrayElements, Result, StatementSequence, Value } from '../types'
import assert from '../utils/assert'
import { filterImportDeclarations } from '../utils/ast/helpers'
import * as ast from '../utils/astCreator'
import { evaluateBinaryExpression, evaluateUnaryExpression } from '../utils/operators'
import * as rttc from '../utils/rttc'
import { checkProgramForUndefinedVariables } from '../validator/validator'
import * as seq from '../utils/statementSeqTransform'
import {
Continuation,
Expand Down
2 changes: 1 addition & 1 deletion src/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { UndefinedImportError } from '../modules/errors'
import { initModuleContextAsync, loadModuleBundleAsync } from '../modules/moduleLoaderAsync'
import type { ImportTransformOptions } from '../modules/moduleTypes'
import { parse } from '../parser/parser'
import { checkProgramForUndefinedVariables } from '../transpiler/transpiler'
import {
BlockExpression,
Context,
Expand All @@ -29,6 +28,7 @@ import {
} from '../utils/dummyAstCreator'
import { evaluateBinaryExpression, evaluateUnaryExpression } from '../utils/operators'
import * as rttc from '../utils/rttc'
import { checkProgramForUndefinedVariables } from '../validator/validator'
import { nodeToValue, objectToString, valueToExpression } from './converter'
import * as builtin from './lib'
import {
Expand Down
242 changes: 8 additions & 234 deletions src/transpiler/transpiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { partition } from 'lodash'
import { RawSourceMap, SourceMapGenerator } from 'source-map'

import { NATIVE_STORAGE_ID, REQUIRE_PROVIDER_ID, UNKNOWN_LOCATION } from '../constants'
import { UndefinedVariable } from '../errors/errors'
import { ModuleNotFoundError } from '../errors/moduleErrors'
import { ModuleInternalError, UndefinedImportError } from '../modules/errors'
import {
Expand All @@ -15,15 +14,13 @@ import {
memoizedGetModuleManifestAsync
} from '../modules/moduleLoaderAsync'
import type { ImportTransformOptions } from '../modules/moduleTypes'
import { parse } from '../parser/parser'
import {
AllowedDeclarations,
Chapter,
Context,
NativeStorage,
Node,
RecursivePartial,
StatementSequence,
Variant
} from '../types'
import assert from '../utils/assert'
Expand All @@ -33,32 +30,19 @@ import {
getFunctionDeclarationNamesInProgram,
getIdentifiersInNativeStorage,
getIdentifiersInProgram,
getUniqueId
getNativeIds,
getUniqueId,
NativeIds
} from '../utils/uniqueIds'
import { ancestor, simple } from '../utils/walkers'
import { simple } from '../utils/walkers'
import { checkForUndefinedVariables } from '../validator/validator'

/**
* This whole transpiler includes many many many many hacks to get stuff working.
* Order in which certain functions are called matter as well.
* There should be an explanation on it coming up soon.
*/

const globalIdNames = [
'native',
'callIfFuncAndRightArgs',
'boolOrErr',
'wrap',
'wrapSourceModule',
'unaryOp',
'binaryOp',
'throwIfTimeout',
'setProp',
'getProp',
'builtins'
] as const

export type NativeIds = Record<(typeof globalIdNames)[number], es.Identifier>

export async function transformImportDeclarations(
program: es.Program,
usedIdentifiers: Set<string>,
Expand Down Expand Up @@ -361,209 +345,6 @@ function transformCallExpressionsToCheckIfFunction(program: es.Program, globalId
})
}

export function checkForUndefinedVariables(
program: es.Program,
nativeStorage: NativeStorage,
globalIds: NativeIds,
skipUndefined: boolean
) {
const builtins = nativeStorage.builtins
const identifiersIntroducedByNode = new Map<Node, Set<string>>()
function processBlock(node: es.Program | es.BlockStatement) {
const identifiers = new Set<string>()
for (const statement of node.body) {
if (statement.type === 'VariableDeclaration') {
identifiers.add((statement.declarations[0].id as es.Identifier).name)
} else if (statement.type === 'FunctionDeclaration') {
if (statement.id === null) {
throw new Error(
'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.'
)
}
identifiers.add(statement.id.name)
} else if (statement.type === 'ImportDeclaration') {
for (const specifier of statement.specifiers) {
identifiers.add(specifier.local.name)
}
}
}
identifiersIntroducedByNode.set(node, identifiers)
}
function processFunction(
node: es.FunctionDeclaration | es.ArrowFunctionExpression,
_ancestors: Node[]
) {
identifiersIntroducedByNode.set(
node,
new Set(
node.params.map(id =>
id.type === 'Identifier'
? id.name
: ((id as es.RestElement).argument as es.Identifier).name
)
)
)
}
const identifiersToAncestors = new Map<es.Identifier, Node[]>()
ancestor(program, {
Program: processBlock,
BlockStatement: processBlock,
FunctionDeclaration: processFunction,
ArrowFunctionExpression: processFunction,
ForStatement(forStatement: es.ForStatement, ancestors: Node[]) {
const init = forStatement.init!
if (init.type === 'VariableDeclaration') {
identifiersIntroducedByNode.set(
forStatement,
new Set([(init.declarations[0].id as es.Identifier).name])
)
}
},
Identifier(identifier: es.Identifier, ancestors: Node[]) {
identifiersToAncestors.set(identifier, [...ancestors])
},
Pattern(node: es.Pattern, ancestors: Node[]) {
if (node.type === 'Identifier') {
identifiersToAncestors.set(node, [...ancestors])
} else if (node.type === 'MemberExpression') {
if (node.object.type === 'Identifier') {
identifiersToAncestors.set(node.object, [...ancestors])
}
}
}
})
const nativeInternalNames = new Set(Object.values(globalIds).map(({ name }) => name))

for (const [identifier, ancestors] of identifiersToAncestors) {
const name = identifier.name
const isCurrentlyDeclared = ancestors.some(a => identifiersIntroducedByNode.get(a)?.has(name))
if (isCurrentlyDeclared) {
continue
}
const isPreviouslyDeclared = nativeStorage.previousProgramsIdentifiers.has(name)
if (isPreviouslyDeclared) {
continue
}
const isBuiltin = builtins.has(name)
if (isBuiltin) {
continue
}
const isNativeId = nativeInternalNames.has(name)
if (!isNativeId && !skipUndefined) {
throw new UndefinedVariable(name, identifier)
}
}
}

export function checkProgramForUndefinedVariables(program: es.Program, context: Context) {
const usedIdentifiers = new Set<string>([
...getIdentifiersInProgram(program),
...getIdentifiersInNativeStorage(context.nativeStorage)
])
const globalIds = getNativeIds(program, usedIdentifiers)

const preludes = context.prelude
? getFunctionDeclarationNamesInProgram(parse(context.prelude, context)!)
: new Set<String>()

const builtins = context.nativeStorage.builtins
const env = context.runtime.environments[0].head

const identifiersIntroducedByNode = new Map<Node, Set<string>>()
function processBlock(node: es.Program | es.BlockStatement | StatementSequence) {
const identifiers = new Set<string>()
for (const statement of node.body) {
if (statement.type === 'VariableDeclaration') {
identifiers.add((statement.declarations[0].id as es.Identifier).name)
} else if (statement.type === 'FunctionDeclaration') {
if (statement.id === null) {
throw new Error(
'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.'
)
}
identifiers.add(statement.id.name)
} else if (statement.type === 'ImportDeclaration') {
for (const specifier of statement.specifiers) {
identifiers.add(specifier.local.name)
}
}
}
identifiersIntroducedByNode.set(node, identifiers)
}
function processFunction(
node: es.FunctionDeclaration | es.ArrowFunctionExpression,
_ancestors: Node[]
) {
identifiersIntroducedByNode.set(
node,
new Set(
node.params.map(id =>
id.type === 'Identifier'
? id.name
: ((id as es.RestElement).argument as es.Identifier).name
)
)
)
}
const identifiersToAncestors = new Map<es.Identifier, Node[]>()
ancestor(program, {
Program: processBlock,
BlockStatement: processBlock,
StatementSequence: processBlock,
FunctionDeclaration: processFunction,
ArrowFunctionExpression: processFunction,
ForStatement(forStatement: es.ForStatement, ancestors: Node[]) {
const init = forStatement.init!
if (init.type === 'VariableDeclaration') {
identifiersIntroducedByNode.set(
forStatement,
new Set([(init.declarations[0].id as es.Identifier).name])
)
}
},
Identifier(identifier: es.Identifier, ancestors: Node[]) {
identifiersToAncestors.set(identifier, [...ancestors])
},
Pattern(node: es.Pattern, ancestors: Node[]) {
if (node.type === 'Identifier') {
identifiersToAncestors.set(node, [...ancestors])
} else if (node.type === 'MemberExpression') {
if (node.object.type === 'Identifier') {
identifiersToAncestors.set(node.object, [...ancestors])
}
}
}
})
const nativeInternalNames = new Set(Object.values(globalIds).map(({ name }) => name))

for (const [identifier, ancestors] of identifiersToAncestors) {
const name = identifier.name
const isCurrentlyDeclared = ancestors.some(a => identifiersIntroducedByNode.get(a)?.has(name))
if (isCurrentlyDeclared) {
continue
}
const isPreviouslyDeclared = context.nativeStorage.previousProgramsIdentifiers.has(name)
if (isPreviouslyDeclared) {
continue
}
const isBuiltin = builtins.has(name)
if (isBuiltin) {
continue
}
const isPrelude = preludes.has(name)
if (isPrelude) {
continue
}
const isInEnv = name in env
if (isInEnv) {
continue
}
const isNativeId = nativeInternalNames.has(name)
if (!isNativeId) {
throw new UndefinedVariable(name, identifier)
}
}
}
function transformSomeExpressionsToCheckIfBoolean(program: es.Program, globalIds: NativeIds) {
function transform(
node:
Expand Down Expand Up @@ -593,14 +374,6 @@ function transformSomeExpressionsToCheckIfBoolean(program: es.Program, globalIds
})
}

function getNativeIds(program: es.Program, usedIdentifiers: Set<string>): NativeIds {
const globalIds = {}
for (const identifier of globalIdNames) {
globalIds[identifier] = create.identifier(getUniqueId(usedIdentifiers, identifier))
}
return globalIds as NativeIds
}

function transformUnaryAndBinaryOperationsToFunctionCalls(
program: es.Program,
globalIds: NativeIds,
Expand Down Expand Up @@ -767,7 +540,8 @@ async function transpileToSource(
transformSomeExpressionsToCheckIfBoolean(program, globalIds)
transformPropertyAssignment(program, globalIds)
transformPropertyAccess(program, globalIds)
checkForUndefinedVariables(program, context.nativeStorage, globalIds, skipUndefined)
checkForUndefinedVariables(program, context, globalIds, skipUndefined)
// checkProgramForUndefinedVariables(program, context, skipUndefined)
transformFunctionDeclarationsToArrowFunctions(program, functionsToStringMap)
wrapArrowFunctionsToAllowNormalCallsAndNiceToString(program, functionsToStringMap, globalIds)
addInfiniteLoopProtection(program, globalIds, usedIdentifiers)
Expand Down Expand Up @@ -815,7 +589,7 @@ async function transpileToFullJS(
])

const globalIds = getNativeIds(program, usedIdentifiers)
checkForUndefinedVariables(program, context.nativeStorage, globalIds, skipUndefined)
checkForUndefinedVariables(program, context, globalIds, skipUndefined)

const [modulePrefix, importNodes, otherNodes] = await transformImportDeclarations(
program,
Expand Down
25 changes: 25 additions & 0 deletions src/utils/uniqueIds.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
import * as es from 'estree'

import { NativeStorage } from '../types'
import * as create from '../utils/astCreator'
import { simple } from '../utils/walkers'

const globalIdNames = [
'native',
'callIfFuncAndRightArgs',
'boolOrErr',
'wrap',
'wrapSourceModule',
'unaryOp',
'binaryOp',
'throwIfTimeout',
'setProp',
'getProp',
'builtins'
] as const

export type NativeIds = Record<(typeof globalIdNames)[number], es.Identifier>

export function getNativeIds(program: es.Program, usedIdentifiers: Set<string>): NativeIds {
const globalIds = {}
for (const identifier of globalIdNames) {
globalIds[identifier] = create.identifier(getUniqueId(usedIdentifiers, identifier))
}
return globalIds as NativeIds
}

export function getUniqueId(usedIdentifiers: Set<string>, uniqueId = 'unique') {
while (usedIdentifiers.has(uniqueId)) {
const start = uniqueId.slice(0, -1)
Expand Down
Loading

0 comments on commit caf8c3a

Please sign in to comment.