Преглед на файлове

Implement array size inference

Implement matrix literal size validation
Lucas de Souza преди 5 години
родител
ревизия
f24078945a
променени са 6 файла, в които са добавени 181 реда и са изтрити 67 реда
  1. 2 1
      i18n/pt/error.json
  2. 11 0
      js/ast/ast_helpers.js
  3. 4 0
      js/ast/error/syntaxErrorFactory.js
  4. 28 24
      js/ast/expressions/arrayLiteral.js
  5. 131 38
      js/ast/ivprogParser.js
  6. 5 4
      js/ast/sourceInfo.js

+ 2 - 1
i18n/pt/error.json

@@ -94,5 +94,6 @@
   "inform_valid_variable_duplicated" : "Já existe uma variável com o nome <span class='ivprog-error-varname'>$0</span> na função <span class='ivprog-error-varname'>$1</span>, você precisa de nomes distintos.",
   "inform_valid_function_duplicated" : "Já existe uma função com o nome <span class='ivprog-error-varname'>$0</span>, você precisa de nomes distintos.",
   "inform_valid_param_duplicated" : "Já existe um parâmetro com o nome <span class='ivprog-error-varname'>$0</span> na função <span class='ivprog-error-varname'>$1</span>, você precisa de nomes distintos.",
-  "invalid_character": "O caractere $0 na linha $1 não pode ser utilizado neste contexto."
+  "invalid_character": "O caractere $0 na linha $1 não pode ser utilizado neste contexto.",
+  "annonymous_array_literal": "Erro na linha $0: a notação de vetor/matriz só permitida durante a inicialização de uma variável desse tipo. Ex: inteiro vec[3] ← {1,2,3}."
 }

+ 11 - 0
js/ast/ast_helpers.js

@@ -0,0 +1,11 @@
+import { SyntaxErrorFactory } from "./error/syntaxErrorFactory";
+
+export function recover (_) {
+    const start = this._tokenStartCharIndex;
+    const stop = this._input.index;
+    let text = this._input.getText(start, stop);
+    text = this.getErrorDisplay(text);
+    const line = this._tokenStartLine;
+    const column = this._tokenStartColumn;
+    throw SyntaxErrorFactory.invalid_character(text, line, column);
+}

+ 4 - 0
js/ast/error/syntaxErrorFactory.js

@@ -72,5 +72,9 @@ export const SyntaxErrorFactory = Object.freeze({
   invalid_character: (text, line, column) => {
     const context = [text, line];
     return new SyntaxError(LocalizedStrings.getError("invalid_character", context));
+  },
+  annonymous_array_literal: (token) => {
+    const context = [token.line];
+    return new SyntaxError(LocalizedStrings.getError("annonymous_array_literal", context));
   }
 });

+ 28 - 24
js/ast/expressions/arrayLiteral.js

@@ -25,7 +25,7 @@ export class ArrayLiteral extends Literal {
     if (!(element instanceof ArrayLiteral)){
       return null;
     } else {
-      return element.value[0].length;
+      return element.value.length;
     }
   }
 
@@ -34,17 +34,18 @@ export class ArrayLiteral extends Literal {
   }
 
   get isValid () {
-    return true;//this.validateType() && this.validateSize();
+    return this.validateSize() && this.validateType();
   }
 
   validateType () {
-    // let valid = true;
+    let valid = true;
+    // const subtype = this.subtype;
     // if(this.columns !== null) {
-    //   const len = this.columns;
-    //   const len2 = this.lines;
-    //   for (let i = len - 1; i >= 0; i--) {
-    //     for (let j = len2 - 1; j >= 0; j--) {
-    //       if(this.value[i].value[j].type !== this.subtype) {
+    //   const len = this.lines;
+    //   const len2 = this.columns;
+    //   for (let i = len - 1; i >= 0 && valid; --i) {
+    //     for (let j = len2 - 1; j >= 0 && valid; --j) {
+    //       if(!this.value[i].value[j].type.isCompatilbe(subtype)) {
     //         valid = false;
     //         break;
     //       }
@@ -52,31 +53,34 @@ export class ArrayLiteral extends Literal {
     //   }
     // } else {
     //   const len = this.lines;
-    //   for (var i = len - 1; i >= 0; i--) {
-    //     if(this.value[i].type !== this.subtype) {
+    //   for (var i = len - 1; i >= 0 && valid; i--) {
+    //     if(!this.value[i].type.isCompatilbe(subtype)) {
     //       valid = false;
     //       break;
     //     }
     //   }
     // }
-    return true;//valid;
+    // Cannot validate type since no information on variables literals are available
+    return valid;
   }
 
   validateSize () {
     let valid = true;
-    if(this.columns !== null) {
-      const equalityTest = data.value.map(v => v.length)
-      .reduce((old, next) => {
-        if (old === null) {
-          return next;
-        } else if (old === next) {
-          return old
-        } else {
-          return -1;
-        }
-      }, null);
-      valid = equalityTest !== -1;
-    }
+    // if(this.columns !== null) {
+    //   const equalityTest = this.value.map(v => v.value.length)
+    //     .reduce((old, next) => {
+    //       console.log(next);
+    //       if (old == null) {
+    //         return next;
+    //       } else if (old === next) {
+    //         return old
+    //       } else {
+    //         return -1;
+    //       }
+    //     }, null);
+    //   valid = equalityTest !== -1;
+    // }
+    // check is now made inside IVProgParser.parseVectorList(...)
     return valid;
   }
 

+ 131 - 38
js/ast/ivprogParser.js

@@ -35,7 +35,8 @@ export class IVProgParser {
 
   constructor (input, lexerClass) {
     this.lexerClass = lexerClass;
-    this.lexer = new lexerClass(new InputStream(input));
+    this.inputStream = new InputStream(input)
+    this.lexer = new lexerClass(this.inputStream);
     this.lexer.recover = AstHelpers.recover.bind(this.lexer);
     this.tokenStream = new CommonTokenStream(this.lexer);
     this.tokenStream.fill();
@@ -233,10 +234,10 @@ export class IVProgParser {
     this.definedFuncsNameList.push(id);
   }
 
-  checkVariableDuplicate (variableID, variableIDToken) {
+  checkVariableDuplicate (variableID, sourceInfo) {
     const index = this.getCurrentVariableStack().indexOf(variableID);
     if(index !== -1) {
-      throw SyntaxErrorFactory.duplicate_variable(variableIDToken);
+      throw SyntaxErrorFactory.duplicate_variable(sourceInfo);
     }
     this.getCurrentVariableStack().push(variableID);
   }
@@ -286,49 +287,91 @@ export class IVProgParser {
     let initial = null;
     let dim1 = null;
     let dim2 = null;
-    const idToken = this.getToken();
+    let dimensions = 0;
+    const sourceInfo = SourceInfo.createSourceInfo(this.getToken())
     const idString = this.parseID();
-    this.checkVariableDuplicate(idString,idToken);
+    this.checkVariableDuplicate(idString, sourceInfo);
     // Check for array or vector
-    // ID[int/IDi][int/IDj]
+    // ID[int/IDi?][int/IDj?]
     if (this.checkOpenBrace(true)) {
-      this.pos++;
+      this.pos += 1;
       this.consumeNewLines();
       dim1 = this.parseArrayDimension();
       this.consumeNewLines();
       this.checkCloseBrace();
-      this.pos++;
+      this.pos += 1;
+      dimensions += 1
       if(this.checkOpenBrace(true)) {
-        this.pos++;
+        this.pos += 1;
         this.consumeNewLines();
         dim2 = this.parseArrayDimension();
         this.consumeNewLines();
         this.checkCloseBrace();
+        this.pos += 1;
+        dimensions += 1
+      }
+      return this.parseArrayDeclaration(typeString, isConst, idString, sourceInfo, dimensions, dim1, dim2);
+    } else {
+      const equalsToken = this.getToken();
+      if(isConst && equalsToken.type !== this.lexerClass.EQUAL ) {
+        throw SyntaxErrorFactory.const_not_init(sourceInfo);
+      }
+      if(equalsToken.type === this.lexerClass.EQUAL) {
+        this.pos++;
+        initial = this.parseExpressionOR();
+      }
+      let declaration =  declaration = new Commands.Declaration(idString, typeString, initial, isConst);
+      declaration.sourceInfo = sourceInfo;
+      const commaToken = this.getToken();
+      if(commaToken.type === this.lexerClass.COMMA) {
         this.pos++;
+        this.consumeNewLines();
+        return [declaration]
+        .concat(this.parseDeclaration(typeString, isConst));
+      } else {
+        return [declaration]
       }
     }
+  }
 
+  parseArrayDeclaration (typeString, isConst, idString, sourceInfo, dimensions, dim1, dim2) {
     const equalsToken = this.getToken();
+    let n_lines = dim1;
+    let n_columns = dim2;
+    let initial = null;
     if(isConst && equalsToken.type !== this.lexerClass.EQUAL ) {
-      throw SyntaxErrorFactory.const_not_init(idToken);
+      throw SyntaxErrorFactory.const_not_init(sourceInfo);
     }
     if(equalsToken.type === this.lexerClass.EQUAL) {
       this.pos++;
-      initial = this.parseExpressionOR();
+      initial = this.parseArrayLiteral(typeString);
     }
-    let declaration = null;
-    let dimensions = 0;
-    if (dim1 !== null) {
-      dimensions++;
-      if(dim2 !== null) {
-        dimensions++;
+    if(dimensions === 1) {
+      if(initial == null && dim1 == null) {
+        // cannot infer dimension
+        throw new Error("cannot infer dimension 1");
       }
-      declaration = new Commands.ArrayDeclaration(idString,
-        new CompoundType(typeString, dimensions), dim1, dim2, initial, isConst);
     } else {
-      declaration = new Commands.Declaration(idString, typeString, initial, isConst);
+      if(initial == null && (dim1 == null || dim2 == null)) {
+        // cannot infer dimension
+        throw new Error("cannot infer dimension 2");
+      }
     }
-    declaration.sourceInfo = SourceInfo.createSourceInfo(idToken);
+
+    if(dim1 == null) {
+      n_lines = new Expressions.IntLiteral(toInt(initial.lines));
+      n_lines.sourceInfo = sourceInfo;
+    }
+
+    if (dimensions > 1) {
+      if(dim2 == null) {
+        n_columns = new Expressions.IntLiteral(toInt(initial.columns));
+        n_columns.sourceInfo = sourceInfo;
+      } 
+    }
+    const declaration = new Commands.ArrayDeclaration(idString,
+      new CompoundType(typeString, dimensions), n_lines, n_columns, initial, isConst);
+    declaration.sourceInfo = sourceInfo;
     const commaToken = this.getToken();
     if(commaToken.type === this.lexerClass.COMMA) {
       this.pos++;
@@ -336,7 +379,7 @@ export class IVProgParser {
       return [declaration]
       .concat(this.parseDeclaration(typeString, isConst));
     } else {
-       return [declaration]
+      return [declaration]
     }
   }
 
@@ -366,6 +409,8 @@ export class IVProgParser {
       //parse as variable
       this.pos++;
       return this.parseVariable(dimToken);
+    } else if(dimToken.type === this.lexerClass.CLOSE_BRACE) {
+      return null;
     } else {
       throw SyntaxErrorFactory.invalid_array_dimension(this.lexer.literalNames[this.lexerClass.RK_INTEGER], dimToken);
     }
@@ -406,39 +451,85 @@ export class IVProgParser {
     return exp;
   }
 
-  parseArrayLiteral () {
+  parseArrayLiteral (typeString) {
     this.checkOpenCurly();
     const beginArray = this.getToken();
     if (this.parsingArrayDimension >= 2) {
       throw SyntaxErrorFactory.token_missing_list(`Array dimensions exceed maximum size of 2 at line ${beginArray.line}`);
     }
-    this.pos++;
-    this.parsingArrayDimension++;
+    this.pos += 1;
+    this.parsingArrayDimension += 1;
     this.consumeNewLines();
-    const data = this.parseExpressionList();
+    let data = null;
+    const maybeCurlyOpen = this.checkOpenCurly(true);
+    if(maybeCurlyOpen) {
+      // This is potentially a list of vectors
+      data = this.parseVectorList(typeString);
+    } else {
+      data = this.parseExpressionList();
+    }
     this.consumeNewLines();
     this.checkCloseCurly()
     const endArray = this.getToken();
-    this.pos++;
-    this.parsingArrayDimension--;
-    if (this.parsingArrayDimension === 0) {
-      // if (!data.isValid) {
-      //   // TODO: better error message
-      //   console.log('invalid array');
-      //   throw new Error(`Invalid array at line ${beginArray.line}`);
-      // }
-    }
+    this.pos += 1;
+    this.parsingArrayDimension -= 1;
     const sourceInfo = SourceInfo.createSourceInfoFromList(beginArray, endArray);
     let dataDim = 1;
     if(data[0] instanceof Expressions.ArrayLiteral) {
-      dataDim++;
+      dataDim += 1;
     }
-    const type = new CompoundType(Types.UNDEFINED, dataDim);
+    const type = new CompoundType(typeString, dataDim);
     const exp = new Expressions.ArrayLiteral(type, data);
     exp.sourceInfo = sourceInfo;
+    if(!exp.isValid) {
+      // invalid array
+      throw new Error("invalid array");
+    }
     return exp;
   }
 
+  /** 
+   * Returns a list of ArrayLiterals. Helper function for parsing matrices
+  */
+  parseVectorList (typeString) {
+    let list = [];
+    let lastSize = null;
+    while(true) {
+      this.checkOpenCurly();
+      const beginArray = this.getToken();
+      if (this.parsingArrayDimension >= 2) {
+        throw SyntaxErrorFactory.token_missing_list(`Array dimensions exceed maximum size of 2 at line ${beginArray.line}`);
+      }
+      this.pos += 1;
+      this.parsingArrayDimension += 1;
+      this.consumeNewLines();
+      const data = this.parseExpressionList();
+      this.consumeNewLines();
+      this.checkCloseCurly()
+      const endArray = this.getToken();
+      this.pos += 1;
+      this.parsingArrayDimension -= 1;
+      if(lastSize == null) {
+        lastSize = data.length;
+      } else if (lastSize !== data.length) {
+        const expString = this.inputStream.getText(beginArray.start, endArray.stop);
+        throw new Error("Invalid size: " + expString);
+      }
+      const sourceInfo = SourceInfo.createSourceInfoFromList(beginArray, endArray);
+      const type = new CompoundType(typeString, 1);
+      const exp = new Expressions.ArrayLiteral(type, data);
+      exp.sourceInfo = sourceInfo;
+      list.push(exp);
+      const commaToken = this.getToken();
+      if(commaToken.type !== this.lexerClass.COMMA) {
+        break;
+      } 
+      this.pos += 1;
+      this.consumeNewLines();
+    }
+    return list
+  }
+
   /*
   * Returns an object {type: 'variable', value: value}.
   * @returns object with fields type and value
@@ -1071,7 +1162,9 @@ export class IVProgParser {
         this.pos++;
         return this.getBoolLiteral(token);
       case this.lexerClass.OPEN_CURLY:
-        return this.parseArrayLiteral();
+        // No more annonymous array
+        // return this.parseArrayLiteral();
+        throw SyntaxErrorFactory.annonymous_array_literal(token);
       case this.lexerClass.ID:
       case this.lexerClass.LIB_ID:
         return this.parseIDTerm();

+ 5 - 4
js/ast/sourceInfo.js

@@ -1,21 +1,22 @@
 export class SourceInfo {
 
   static createSourceInfo (token) {
-    return new SourceInfo(token.line, token.column, token.text.length);
+    return new SourceInfo(token.line, token.column, token.text, token.text.length);
   }
 
   static createSourceInfoFromList (tokenA, tokenB) {
     const line = tokenA.line;
     const column = tokenA.column;
-    // copied from https://github.com/UNIVALI-LITE/Portugol-Studio/blob/master/core/src/main/java/br/univali/portugol/nucleo/analise/sintatica/Portugol.g
+    // adapted from https://github.com/UNIVALI-LITE/Portugol-Studio/blob/master/core/src/main/java/br/univali/portugol/nucleo/analise/sintatica/Portugol.g
     // No idea why...
     const size = tokenB.tokenIndex + 1 - tokenA.tokenIndex
-    return new SourceInfo(line, column, size);
+    return new SourceInfo(line, column, "", size);
   }
 
-  constructor (line, column, size) {
+  constructor (line, column, text, size) {
     this.line = line;
     this.column = column;
+    this.text = text;
     this.size = size;
   }