diff --git a/lib/cli.js b/lib/cli.js index 2a0aa9d..14fe720 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -79,9 +79,9 @@ const evalFunction = (argsObj, infile, outfile) => { const core = libName ? importCore(libName) : '' const tree = parser(program) if (tree.error) showAndExit(makeErrStr(tree, infile)) + const maybeTypeErr = argsObj.t ? null : inferTypes(tree.body) + if (maybeTypeErr !== null) showAndExit(maybeTypeErr.message) if (argsObj.ast) showAndExit(JSON.stringify(tree, null, 2)) - const maybeTypeErr = argsObj.t ? tree.body : inferTypes(tree.body) - if (maybeTypeErr.error) showAndExit(JSON.stringify(maybeTypeErr)) const out = escodegen.generate(tree, { format, comment: true }) fs.writeFileSync(outfile, core + out + '\n', 'utf8') const commandLineArgs = argsObj._.slice(1) diff --git a/lib/typeInference.js b/lib/typeInference.js index b3d2609..d6b34a4 100644 --- a/lib/typeInference.js +++ b/lib/typeInference.js @@ -7,7 +7,9 @@ const inbuiltPropSpec = require('./inbuiltMethods').inbuiltProps const inbuiltObjects = require('./inbuiltMethods').inbuiltObjects const jsFunctions = require('./jsFunctions') const isEmptyObj = require('./utilityFunctions').isEmptyObj + const globalTypesObj = {} +const funcCallTypesObj = {} const isFunction = (id, typeObj) => typeObj[id] !== undefined && @@ -44,6 +46,7 @@ const unaryExprType = (expr) => { const getType = (expr, expectedType, localTypeObj, id) => { if (expr !== undefined) { const type = exprType(expr, localTypeObj, id) + if (expr.type === 'Identifier') return type return type === expectedType || expectedType === 'bool' ? type : type === 'needsInference' @@ -73,8 +76,12 @@ const checkTypes = (expr, expectedType, localTypeObj, id) => { const assignTypeToUninferredIdentifier = (expr, inferredType, localTypeObj) => { if (isEmptyObj(localTypeObj)) return localTypeObj const [left, right] = [expr.left.name, expr.right.name] - if (left !== undefined && localTypeObj[left] === 'needsInference') { localTypeObj[left] = inferredType } - if (right !== undefined && localTypeObj[right] === 'needsInference') { localTypeObj[right] = inferredType } + if (left !== undefined && localTypeObj[left] === 'needsInference') { + localTypeObj[left] = inferredType + } + if (right !== undefined && localTypeObj[right] === 'needsInference') { + localTypeObj[right] = inferredType + } } const binaryExprType = (expr, localTypeObj, id) => { @@ -134,13 +141,13 @@ const conditionalExprType = (expr, localTypeObj, id) => { return consequentType === alternateType ? consequentType : null } -const mapArgTypeToParams = (params, args, localTypeObj, id) => { - return params.map((param, index) => - args[index].sType === undefined - ? exprType(args[index], localTypeObj, id) - : args[index].sType - ) -} +// const mapArgTypeToParams = (params, args, localTypeObj, id) => { +// return params.map((param, index) => +// args[index].sType === undefined +// ? exprType(args[index], localTypeObj, id) +// : args[index].sType +// ) +// } const makeParamTypeArray = (localTypeObj) => { const paramTypes = [] @@ -217,33 +224,34 @@ const checkForRecursion = (callee, id, localTypeObj, args) => { const getFunctionType = (calleeName) => jsFunctions[calleeName] const callExprType = (expr, localTypeObj = {}, id) => { + const calleeName = expr.callee.name if ( expr.callee.type === 'Identifier' && - globalTypesObj[expr.callee.name] === undefined + globalTypesObj[calleeName] === undefined ) { - const calleeName = expr.callee.name globalTypesObj[calleeName] = getFunctionType(calleeName) } - const [params, body, args] = [ - expr.callee.params, - expr.callee.body, - expr.arguments - ] - if (expr.sType !== undefined) return expr.sType // if call has type already set - if ( - params !== undefined && - body !== undefined && - (!isEmptyObj(localTypeObj) || expr.callee.id === null) - ) { - const typesOfParams = mapArgTypeToParams(params, args, localTypeObj, id) - args.map((arg) => exprType(arg, localTypeObj, id)) - params.map((param, index) => { - localTypeObj[param.name] = typesOfParams[index] - return param - }) - return exprType(body, localTypeObj, id) - } - const isRecursive = checkForRecursion(expr.callee.name, id, localTypeObj, args) + const args = expr.arguments + // const [params, body, args] = [ + // expr.callee.params, + // expr.callee.body, + // expr.arguments + // ] + // if (expr.sType !== undefined) return expr.sType // if call has type already set + // if ( + // params !== undefined && + // body !== undefined && + // (!isEmptyObj(localTypeObj) || expr.callee.id === null) + // ) { + // const typesOfParams = mapArgTypeToParams(params, args, localTypeObj, id) + // args.map((arg) => exprType(arg, localTypeObj, id)) + // params.map((param, index) => { + // localTypeObj[param.name] = typesOfParams[index] + // return param + // }) + // return exprType(body, localTypeObj, id) + // } + const isRecursive = checkForRecursion(calleeName, id, localTypeObj, args) let result = exprType(expr.callee, localTypeObj, id) if (result === null) return null if (expr.callee.id === null) result = [result.paramTypes, result.returnType] @@ -255,13 +263,19 @@ const callExprType = (expr, localTypeObj = {}, id) => { localTypeObj, id ) - return match ? returnType : null + returnType = match ? returnType : null + if (calleeName) { + funcCallTypesObj[calleeName] = returnType + } + return returnType } const reduceTypes = (typesArr, localTypeObj, id) => { const reducedType = typesArr .map((e) => { - if (e.type !== undefined && e.type === 'Identifier') { return { id: e.name, type: exprType(e, localTypeObj, id) } } + if (e.type !== undefined && e.type === 'Identifier') { + return { id: e.name, type: exprType(e, localTypeObj, id) } + } return { id: null, type: exprType(e, localTypeObj, id) } }) .reduce((exp1, exp2) => { @@ -278,39 +292,34 @@ const reduceTypes = (typesArr, localTypeObj, id) => { localTypeObj[exp2.id] = exp1.type } if (typeof exp1.type === 'object' && typeof exp2.type === 'object') { - return exp1.type.type === exp2.type.type ? exp1.type : false + return exp1.type.type === exp2.type.type ? exp1.type : null } - return exp1.type === exp2.type ? exp1 : false + return exp1.type === exp2.type ? exp1 : null }) - return reducedType !== false ? reducedType.type : false + return reducedType !== null ? reducedType.type : null } const switchStatementType = (body, localTypeObj, id) => { const cases = body.cases - const [initialPattern] = cases - let [returnType, acceptedType] = [ - initialPattern.consequent[0].argument, - initialPattern.test - ].map((exp) => exprType(exp, localTypeObj, id)) + const initialPattern = cases[0] + let returnType = exprType(initialPattern.consequent[0].argument, localTypeObj, id) + const acceptedType = exprType(initialPattern.test, localTypeObj, id) const paramId = body.discriminant.name localTypeObj[paramId] = acceptedType - const [caseArgArray, caseTestArray] = [ - cases.map((c) => c.consequent[0].argument), - cases.map((c) => - c.test === null - ? { type: 'Identifier', name: paramId, sType: acceptedType } - : c.test - ) - ] - const checkTestType = reduceTypes(caseTestArray, localTypeObj, id) - const checkArgsType = reduceTypes(caseArgArray, localTypeObj, id) - if (returnType === 'needsInference') returnType = checkArgsType + const caseConseqArray = cases.map((c) => c.consequent[0].argument) + const caseTestArray = cases.map((c) => c.test === null + ? { type: 'Identifier', name: paramId, sType: acceptedType } + : c.test + ) + const conseqType = reduceTypes(caseConseqArray, localTypeObj, id) + const testType = reduceTypes(caseTestArray, localTypeObj, id) + if (returnType === 'needsInference') returnType = conseqType globalTypesObj[id] = { type: 'function', paramTypes: makeParamTypeArray(localTypeObj, id), returnType } - return checkArgsType === false || checkTestType === false ? null : returnType + return (conseqType && testType) ? returnType : null } const arrayExprType = (expr, localTypeObj, id) => { @@ -537,7 +546,6 @@ const blockStatementType = (stmnt, localTypeObj, id) => { const exprType = (expr, localTypeObj = {}, id = null) => { if (expr !== null && expr.sType !== undefined && expr.sType === 'IO') { return 'IO' } if (expr === null) return 'needsInference' - if (expr.type === 'ExpressionStatement') expr = expr.expression const type = expr.type switch (type) { case 'Literal': @@ -579,6 +587,21 @@ const declTypeExtract = (stmnt) => { globalTypesObj[id] = type } } +const exprTypeExtract = (stmnt) => { + const expr = stmnt.expression + if (expr.sType === 'IO' && !isIOCall(expr)) { + const args = expr.arguments + args.forEach(arg => exprType(arg)) + } else if (!isIOCall(expr)) { + exprType(expr) + } +} + +const isIOCall = (expr) => { + if (!expr.callee.object) return false + const method = expr.callee.object.name + return globalTypesObj[method] === 'IO' +} const loadInbuiltObjects = () => { for (const obj in inbuiltObjects) { @@ -592,22 +615,43 @@ const delObj = (obj) => { } } -const types = (body) => { - loadInbuiltObjects() - body.forEach((expr) => { - const type = expr.type - if (type === 'VariableDeclaration') declTypeExtract(expr) - }) - const errorObj = { error: false } +const errorObj = { error: false } + +const makeTypeError = (error) => { + return `TypeError at ${error.type} '${error.id}'` +} + +const checkForErrors = () => { for (const decl in globalTypesObj) { if (globalTypesObj[decl] === null) { errorObj.error = true errorObj.id = decl - break + errorObj.type = 'VariableDeclaration' + return } } + for (const call in funcCallTypesObj) { + if (funcCallTypesObj[call] === null) { + errorObj.error = true + errorObj.id = call + errorObj.type = 'CallExpression' + return + } + } +} + +const types = (body) => { + delObj(errorObj) + loadInbuiltObjects() + body.forEach((expr) => { + const type = expr.type + if (type === 'VariableDeclaration') declTypeExtract(expr) + else if (type === 'ExpressionStatement') exprTypeExtract(expr) + }) + checkForErrors() delObj(globalTypesObj) - return errorObj.error ? errorObj : body + delObj(funcCallTypesObj) + return errorObj.error ? { ...errorObj, message: makeTypeError(errorObj) } : null } /* Module Exports types */ diff --git a/test/assert/funcDecl.json b/test/assert/funcDecl.json index 1ca1b31..7b0efa4 100644 --- a/test/assert/funcDecl.json +++ b/test/assert/funcDecl.json @@ -121,6 +121,24 @@ } ], "kind": "const" + }, + { + "type": "ExpressionStatement", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Identifier", + "name": "fact" + }, + "arguments": [ + { + "type": "Literal", + "value": 10, + "raw": "10", + "sType": "number" + } + ] + } } ], "sourceType": "script" diff --git a/test/assert/typeErrors/funcDeclWithTypeError.json b/test/assert/typeErrors/funcDeclWithTypeError.json new file mode 100644 index 0000000..1ff9502 --- /dev/null +++ b/test/assert/typeErrors/funcDeclWithTypeError.json @@ -0,0 +1,7 @@ +{ + "error": true, + "id": "fact", + "message": "TypeError at CallExpression 'fact'", + "type": "CallExpression" +} + diff --git a/test/assert/typeErrors/typeError.json b/test/assert/typeErrors/typeError.json index 4d2d1a2..dd6de37 100644 --- a/test/assert/typeErrors/typeError.json +++ b/test/assert/typeErrors/typeError.json @@ -1 +1,6 @@ -{ "error": true, "id": "a" } +{ + "error": true, + "id": "a", + "message": "TypeError at VariableDeclaration 'a'", + "type": "VariableDeclaration" +} diff --git a/test/sourceFiles.test.js b/test/sourceFiles.test.js index 3e9efdd..39e0638 100644 --- a/test/sourceFiles.test.js +++ b/test/sourceFiles.test.js @@ -20,9 +20,8 @@ const generateTree = input => { } const tree = parser(rest) if (tree.error) return tree - const newTree = typeInfer(tree.body) - if (newTree.error) return newTree - tree.body = newTree + const maybeTypeErrors = typeInfer(tree.body) + if (maybeTypeErrors !== null) return maybeTypeErrors return tree } diff --git a/test/src/funcDecl.cl b/test/src/funcDecl.cl index fcce681..62dfdac 100644 --- a/test/src/funcDecl.cl +++ b/test/src/funcDecl.cl @@ -1,3 +1,5 @@ fact 0 = 1 fact 1 = 1 fact n = n * fact (n - 1) + +fact 10 diff --git a/test/src/typeErrors/funcDeclWithTypeError.cl b/test/src/typeErrors/funcDeclWithTypeError.cl new file mode 100644 index 0000000..65d7e79 --- /dev/null +++ b/test/src/typeErrors/funcDeclWithTypeError.cl @@ -0,0 +1,5 @@ +fact 0 = 1 +fact 1 = 1 +fact n = n * fact (n - 1) + +fact 'a'