Переглянути джерело

Implement Semantic Analysis with type check

-Still need to implement the correct error messages
Lucas de Souza 6 роки тому
батько
коміт
1575835d14

+ 132 - 9
js/processor/semantic/semanticAnalyser.js

@@ -1,7 +1,7 @@
 import { ProcessorErrorFactory } from './../error/processorErrorFactory';
 import { LanguageDefinedFunction } from './../definedFunctions';
 import { LanguageService } from './../../services/languageService';
-import { ArrayDeclaration } from '../../ast/commands';
+import { ArrayDeclaration, While, For, Switch, Case, Declaration, Assign, Break, IfThenElse, Return } from '../../ast/commands';
 import { InfixApp, UnaryApp, FunctionCall, IntLiteral, RealLiteral, StringLiteral, BoolLiteral, VariableLiteral, ArrayLiteral } from '../../ast/expressions';
 import { Literal } from '../../ast/expressions/literal';
 import { resultTypeAfterInfixOp, resultTypeAfterUnaryOp } from '../compatibilityTable';
@@ -69,7 +69,14 @@ export class SemanticAnalyser {
     this.pushMap();
     this.assertDeclarations(globalVars);
     const functions = this.ast.functions;
+    const mainFunc = functions.filter((f) => f.name === null);
+    if (mainFunc.length <= 0) {
+      throw new Error("no main func...");
+    } else if (mainFunc.length > 1) {
+      throw new Error("only one main func...");
+    }
     for (let i = 0; i < functions.length; i++) {
+
       const fun = functions[i];
       this.assertFunction(fun);
     }
@@ -85,6 +92,16 @@ export class SemanticAnalyser {
   assertDeclaration (declaration) {
     if (declaration instanceof ArrayDeclaration) {
       if(declaration.initial === null) {
+        const lineType = this.evaluateExpressionType(declaration.lines);
+        if (lineType !== Types.INTEGER) {
+          throw new Error("dim must be int");
+        }
+        if (declaration.columns !== null) {
+          const columnType = this.evaluateExpressionType(declaration.columns);
+          if (columnType !== Types.INTEGER) {
+            throw new Error("dim must be int");
+          }
+        }
         this.insertSymbol(declaration.id, {id: declaration.id, lines: declaration.lines, columns: declaration.columns, type: declaration.type, subtype: declaration.subtype});
         return;
       }
@@ -152,7 +169,11 @@ export class SemanticAnalyser {
     if (literal instanceof ArrayLiteral) {
       if (columns === null) {
         // it's a vector...
-        if (lines !== literal.value.length) {
+        const dimType = this.evaluateExpressionType(lines);
+        if (dimType !== Types.INTEGER) {
+          throw new Error("dim must be int");
+        }
+        if ((lines instanceof IntLiteral) && lines.value !== literal.value.length) {
           throw new Error("invalid array size");
         }
         literal.value.reduce((last, next) => {
@@ -164,7 +185,11 @@ export class SemanticAnalyser {
         });
         return true;
       } else {
-        if (columns !== literal.value.length) {
+        const dimType = this.evaluateExpressionType(columns);
+        if (dimType !== Types.INTEGER) {
+          throw new Error("dim must be int");
+        }
+        if ((columns instanceof IntLiteral) && columns.value !== literal.value.length) {
           throw new Error("invalid array size");
         }
         for (let i = 0; i < columns; i++) {
@@ -194,16 +219,111 @@ export class SemanticAnalyser {
   assertFunction (fun) {
     this.pushMap();
     this.assertDeclarations(fun.variablesDeclarations);
-    if(fun.returnType === Types.VOID) {
-      this.assertOptionalReturn(fun);
-    } else {
-      this.assertReturn(fun);
+    const optional = fun.returnType === Types.VOID;
+    const valid = this.assertReturn(fun, optional);
+    if (!valid) {
+      throw new Error("function has no accessible return");
     }
     this.popMap();
   }
 
-  assertOptionalReturn (fun) {
+  assertReturn (fun, optional) {
+    return fun.commands.reduce(
+      (last, next) => this.checkCommand(fun.returnType, next, optional) || last, optional
+    );
+  }
 
+  checkCommand (type, cmd, optional) {
+    if (cmd instanceof While) {
+      const resultType = this.evaluateExpressionType(cmd.expression);
+      if (resultType !== Types.BOOLEAN) {
+        throw new Error("condition not boolean");
+      }
+      this.checkCommands(type, cmd.commands, optional);
+      return false;
+    } else if (cmd instanceof For) {
+      this.checkCommand(type, cmd.assignment, optional);
+      const resultType = this.evaluateExpressionType(cmd.condition);
+      if (resultType !== Types.BOOLEAN) {
+        throw new Error("condition not boolean");
+      }
+      this.checkCommand(type, cmd.increment, optional);
+      this.checkCommands(type, cmd.commands, optional);
+      return false;
+    } else if (cmd instanceof Switch) {
+      const sType = this.evaluateExpressionType(cmd.expression);
+      let result = optional;
+      let hasDefault = false;
+      for (let i = 0; i < cmd.cases.length; i++) {
+        const aCase = cmd.cases[i];
+        if (aCase.expression !== null) {
+          const caseType = this.evaluateExpressionType(aCase.expression);
+          if (sType !== caseType) {
+            throw new Error("invalid type in case");
+          }
+        } else {
+          hasDefault = true;
+        }
+        result = result && this.checkCommands(type, aCase.commands, result);        
+      }
+      return result && hasDefault;
+
+    } else if (cmd instanceof Assign) {
+      const typeInfo = this.findSymbol(cmd.id, this.symbolMap);
+      const exp = cmd.expression;
+      if(exp instanceof ArrayLiteral) {
+        if(!typeInfo.subtype) {
+          throw new Error("type not compatible");
+        }
+        this.evaluateArrayLiteral(typeInfo.lines, typeInfo.columns, typeInfo.subtype, exp);
+      } else {
+        if(typeInfo.subtype) {
+          throw new Error("type not compatible");
+        }
+        const resultType = this.evaluateExpressionType(exp);
+        if(resultType !== typeInfo.type) {
+          throw new Error("type not compatible");
+        }
+      }
+      return optional;
+    } else if (cmd instanceof Break) {
+      return optional;
+    } else if (cmd instanceof IfThenElse) {
+      const resultType = this.evaluateExpressionType(cmd.condition);
+      if (resultType !== Types.BOOLEAN) {
+        throw new Error("condition not boolean");
+      }
+      console.log(cmd);
+      if(cmd.ifFalse instanceof IfThenElse) {
+        return this.checkCommands(type, cmd.ifTrue.commands, optional) && this.checkCommand(type, cmd.ifFalse, optional);
+      } else {
+        return this.checkCommands(type, cmd.ifTrue.commands, optional) && this.checkCommands(type, cmd.ifFalse.commands,optional);
+      }
+
+    } else if (cmd instanceof FunctionCall) {
+      const fun = this.findFunction(cmd.id);
+      this.assertParameters(fun, cmd.actualParameters);
+      return optional;
+    } else if (cmd instanceof Return) {
+      if (cmd.expression === null && type !== Types.VOID) {
+        throw new Error('invalid return type');
+      } else if (cmd.expression !== null) {
+        const resultType = this.evaluateExpressionType(cmd.expression);
+        if (resultType !== type) {
+          throw new Error('invalid return type');
+        } else {
+          return true;
+        }
+      } else {
+        return true;
+      }
+    }
+  }
+
+  checkCommands (type, cmds, optional) {
+    return cmds.reduce(
+      (last, next) => this.checkCommand(type, next, optional) || last, optional
+    );
   }
 
   assertParameters (fun, actualParametersList) {
@@ -239,7 +359,10 @@ export class SemanticAnalyser {
           break;
         }
         default: {
-          if (resultType.subtype || resultType !== formalParam.type) {
+          if (resultType.subtype) {
+            throw new Error("invalid param type");
+          }
+          if (formalParam.type !== Types.ALL && resultType !== formalParam.type) {
             throw new Error("invalid param type");
           }
           break;

+ 26 - 0
tests/test44.spec.js

@@ -0,0 +1,26 @@
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { SemanticAnalyser } from './../js/processor/semantic/semanticAnalyser';
+import { LanguageService } from '../js/services/languageService';
+
+describe('A valid code', function () {
+
+  const code = `programa {
+
+    funcao inicio() {
+      real a;
+      leia(a);
+      a = a + 0xff
+    }
+  }`;
+
+  localStorage.setItem('ivprog.lang', 'pt');
+
+  const lexer = LanguageService.getCurrentLexer();
+
+  it(`should not throw a semantic error`, function () {
+    const parser = new IVProgParser(code, lexer);
+    const sem = new SemanticAnalyser(parser.parseTree());
+    const fun = sem.analyseTree.bind(sem);
+    expect(fun).not.toThrow();
+  });
+});

+ 29 - 0
tests/test45.spec.js

@@ -0,0 +1,29 @@
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { SemanticAnalyser } from './../js/processor/semantic/semanticAnalyser';
+import { LanguageService } from '../js/services/languageService';
+
+describe('The semantic analyser', function () {
+
+  const code = `programa {
+
+    funcao inicio() {
+      real a;
+      a = aNumber()
+    }
+
+    funcao inteiro aNumber () {
+      retorne 3
+    }
+  }`;
+
+  localStorage.setItem('ivprog.lang', 'pt');
+
+  const lexer = LanguageService.getCurrentLexer();
+
+  it(`should type check`, function () {
+    const parser = new IVProgParser(code, lexer);
+    const sem = new SemanticAnalyser(parser.parseTree());
+    const fun = sem.analyseTree.bind(sem);
+    expect(fun).toThrow();
+  });
+});

+ 25 - 0
tests/test46.spec.js

@@ -0,0 +1,25 @@
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { SemanticAnalyser } from './../js/processor/semantic/semanticAnalyser';
+import { LanguageService } from '../js/services/languageService';
+
+describe('The semantic analyser', function () {
+
+  const code = `programa {
+
+    funcao inicio() {
+      inteiro a = 5
+      inteiro b[a];
+    }
+  }`;
+
+  localStorage.setItem('ivprog.lang', 'pt');
+
+  const lexer = LanguageService.getCurrentLexer();
+
+  it(`should ignore size check on variable as dimensions`, function () {
+    const parser = new IVProgParser(code, lexer);
+    const sem = new SemanticAnalyser(parser.parseTree());
+    const fun = sem.analyseTree.bind(sem);
+    expect(fun).not.toThrow();
+  });
+});

+ 26 - 0
tests/test47.spec.js

@@ -0,0 +1,26 @@
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { SemanticAnalyser } from './../js/processor/semantic/semanticAnalyser';
+import { LanguageService } from '../js/services/languageService';
+
+describe('The semantic analyser', function () {
+
+  const code = `programa {
+
+    funcao inicio() {
+      inteiro a = 5
+      real i = 8.5
+      inteiro b[a][i];
+    }
+  }`;
+
+  localStorage.setItem('ivprog.lang', 'pt');
+
+  const lexer = LanguageService.getCurrentLexer();
+
+  it(`should guarantee that array variable as dimensions are integers`, function () {
+    const parser = new IVProgParser(code, lexer);
+    const sem = new SemanticAnalyser(parser.parseTree());
+    const fun = sem.analyseTree.bind(sem);
+    expect(fun).toThrow();
+  });
+});

+ 25 - 0
tests/test48.spec.js

@@ -0,0 +1,25 @@
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { SemanticAnalyser } from './../js/processor/semantic/semanticAnalyser';
+import { LanguageService } from '../js/services/languageService';
+
+describe('The semantic analyser', function () {
+
+  const code = `programa {
+
+    funcao inicio() {
+      inteiro a = 5
+      inteiro b[a][i];
+    }
+  }`;
+
+  localStorage.setItem('ivprog.lang', 'pt');
+
+  const lexer = LanguageService.getCurrentLexer();
+
+  it(`should not allow usage of not defined variable`, function () {
+    const parser = new IVProgParser(code, lexer);
+    const sem = new SemanticAnalyser(parser.parseTree());
+    const fun = sem.analyseTree.bind(sem);
+    expect(fun).toThrow();
+  });
+});

+ 36 - 0
tests/test49.spec.js

@@ -0,0 +1,36 @@
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { SemanticAnalyser } from './../js/processor/semantic/semanticAnalyser';
+import { LanguageService } from '../js/services/languageService';
+
+describe('The semantic analyser', function () {
+
+  const code = `programa {
+
+    const inteiro V = 1
+
+    funcao inicio() {
+      inteiro a = 5
+      a = aNumber()
+    }
+
+    funcao inteiro aNumber () {
+      inteiro a = 5
+      se (V == 1) {
+        retorne a + 3
+      } senao {
+        a = a * 2
+      }
+    }
+  }`;
+
+  localStorage.setItem('ivprog.lang', 'pt');
+
+  const lexer = LanguageService.getCurrentLexer();
+
+  it(`should guarantee that a function has an accessible return`, function () {
+    const parser = new IVProgParser(code, lexer);
+    const sem = new SemanticAnalyser(parser.parseTree());
+    const fun = sem.analyseTree.bind(sem);
+    expect(fun).toThrow();
+  });
+});