ソースを参照

Implement some expressions conversions to ANSI C

Lucas de Souza 4 年 前
コミット
dfde1bcedc

+ 35 - 31
.eslintrc.json

@@ -1,33 +1,37 @@
 {
-    "env": {
-        "browser": true,
-        "es6": true
-    },
-    "parser": "@typescript-eslint/parser",
-    "plugins": ["@typescript-eslint"],
-    "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
-    "globals": {
-        "Atomics": "readonly",
-        "SharedArrayBuffer": "readonly"
-    },
-    "parserOptions": {
-        "ecmaVersion": 2018,
-        "sourceType": "module"
-    },
-    "rules": {
-        "camelcase": "off",
-        "no-unused-vars": "off",
-        "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
-        "@typescript-eslint/camelcase": "off"
+  "env": {
+    "browser": true,
+    "es6": true
+  },
+  "parser": "@typescript-eslint/parser",
+  "plugins": ["@typescript-eslint"],
+  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
+  "globals": {
+    "Atomics": "readonly",
+    "SharedArrayBuffer": "readonly"
+  },
+  "parserOptions": {
+    "ecmaVersion": 2018,
+    "sourceType": "module"
+  },
+  "rules": {
+    "camelcase": "off",
+    "no-unused-vars": "off",
+    "@typescript-eslint/no-unused-vars": [
+      "error",
+      { "argsIgnorePattern": "^_" }
+    ],
+    "@typescript-eslint/camelcase": "off",
+    "space-before-function-paren": ["error", "always"]
+  },
+  "overrides": [
+    {
+      "files": ["*.js", "*.jsx"],
+      "rules": {
+        "@typescript-eslint/explicit-member-accessibility": 0,
+        "@typescript-eslint/explicit-function-return-type": 0
+      }
     }
-    ,
-    "overrides": [
-        {
-            "files": ["*.js", "*.jsx"],
-            "rules": {
-                "@typescript-eslint/explicit-member-accessibility": 0,
-                "@typescript-eslint/explicit-function-return-type": 0
-            }
-        }
-    ]    
-}
+  ]
+}
+

+ 13 - 13
js/ast/commands/commandBlock.js

@@ -1,16 +1,16 @@
 export class CommandBlock {
-	
-	constructor(variables, commands) {
-		this.variables = variables;
-		this.commands = commands;
-		this._sourceInfo = null;
-	}
+  constructor (variables, commands) {
+    this.variables = variables;
+    this.commands = commands;
+    this._sourceInfo = null;
+  }
 
-	set sourceInfo (sourceInfo) {
-		this._sourceInfo = sourceInfo;
-	}
+  set sourceInfo (sourceInfo) {
+    this._sourceInfo = sourceInfo;
+  }
+
+  get sourceInfo () {
+    return this._sourceInfo;
+  }
+}
 
-	get sourceInfo () {
-		return this._sourceInfo;
-	}
-}

+ 10 - 6
js/ast/expressions/variableLiteral.js

@@ -1,18 +1,22 @@
-import { Literal } from './literal';
-import { Types } from './../../typeSystem/types';
+import { Literal } from "./literal";
+import { Types } from "./../../typeSystem/types";
 
 export class VariableLiteral extends Literal {
-  
-  constructor(id) {
+  constructor (id) {
     super(Types.UNDEFINED);
     this.id = id;
   }
 
+  get value () {
+    return this.id;
+  }
+
   toString () {
-    if(this.parenthesis) {
+    if (this.parenthesis) {
       return `(${this.id})`;
     } else {
       return this.id;
     }
   }
-}
+}
+

+ 122 - 0
js/conversion/baseConverter.js

@@ -0,0 +1,122 @@
+import {
+  FunctionCall,
+  Literal,
+  UnaryApp,
+  InfixApp,
+  IntLiteral,
+  RealLiteral,
+  StringLiteral,
+  VariableLiteral,
+  ArrayAccess,
+  BoolLiteral
+} from "./../ast/expressions";
+import { Types } from "./../typeSystem/types";
+import { ArrayType } from "./../typeSystem/array_type";
+import {
+  resultTypeAfterInfixOp,
+  resultTypeAfterUnaryOp
+} from "./../processor/compatibilityTable";
+import { LanguageDefinedFunction } from "./../processor/definedFunctions";
+
+export class BaseConverter {
+  constructor (ivprogAST) {
+    this.ivprogAST = ivprogAST;
+    this.globalMap = new Map();
+    this.symbolMap = [];
+    this.symbolMap.push(this.globalMap);
+  }
+
+  popContext () {
+    if (this.symbolMap.length > 1) {
+      this.symbolMap.pop();
+    }
+  }
+
+  pushContext (map) {
+    this.symbolMap.push(map);
+  }
+
+  findFunction (name) {
+    if (name.match(/^\$.+$/)) {
+      const fun = LanguageDefinedFunction.getFunction(name);
+      return fun;
+    } else {
+      const val = this.ivprogAST.functions.find(v => v.name === name);
+      return val;
+    }
+  }
+
+  findSymbol (id) {
+    for (let i = this.symbolMap.length - 1; i >= 0; i -= 1) {
+      const map = this.symbolMap[i];
+      if (map.has(id)) {
+        return map.get(id);
+      }
+    }
+    return null;
+  }
+
+  insertGlobalSymbol (id, declaration) {
+    this.globalMap.set(id, { id: id, type: declaration.type, global: true });
+  }
+
+  insertSymbol (id, declaration) {
+    const map = this.symbolMap.slice(-1)[0];
+    map.set(id, { id: id, type: declaration.type, global: false });
+  }
+
+  evaluateExpressionType (expression) {
+    if (expression instanceof UnaryApp) {
+      const op = expression.op;
+      const resultType = this.evaluateExpressionType(expression.left);
+      const finalResult = resultTypeAfterUnaryOp(op, resultType);
+      return finalResult;
+    } else if (expression instanceof InfixApp) {
+      const op = expression.op;
+      const resultTypeLeft = this.evaluateExpressionType(expression.left);
+      const resultTypeRight = this.evaluateExpressionType(expression.right);
+      const finalResult = resultTypeAfterInfixOp(
+        op,
+        resultTypeLeft,
+        resultTypeRight
+      );
+      return finalResult;
+    } else if (expression instanceof Literal) {
+      return this.evaluateLiteralType(expression);
+    } else if (expression instanceof FunctionCall) {
+      const fun = this.findFunction(expression.id);
+      return fun.returnType;
+    } else if (expression instanceof ArrayAccess) {
+      const arrayTypeInfo = this.findSymbol(expression.id);
+      const arrType = arrayTypeInfo.type;
+      if (expression.column !== null) {
+        // indexing matrix
+        return arrType.innerType;
+      } else {
+        if (arrayTypeInfo.columns === null) {
+          return arrType.innerType;
+        }
+        return new ArrayType(arrType.innerType, 1);
+      }
+    }
+  }
+
+  evaluateLiteralType (literal) {
+    if (literal instanceof IntLiteral) {
+      return literal.type;
+    } else if (literal instanceof RealLiteral) {
+      return literal.type;
+    } else if (literal instanceof StringLiteral) {
+      return literal.type;
+    } else if (literal instanceof BoolLiteral) {
+      return literal.type;
+    } else if (literal instanceof VariableLiteral) {
+      const typeInfo = this.findSymbol(literal.id);
+      return typeInfo.type;
+    } else {
+      console.warn("Evaluating type only for an array literal...");
+      // This should never happen in this context and there is no use throwing execeptions!
+      return new ArrayType(Types.INTEGER, 1);
+    }
+  }
+}

+ 15 - 0
js/conversion/c/ast/textNode.js

@@ -0,0 +1,15 @@
+import { Expression } from "./../../../ast/expressions/expression";
+
+export class TextNode extends Expression {
+  constructor (value) {
+    super();
+    this.value = value;
+  }
+
+  toString () {
+    if (this.parenthesis) {
+      return `(${this.value})`;
+    }
+    return this.value;
+  }
+}

+ 420 - 0
js/conversion/c/converter.js

@@ -0,0 +1,420 @@
+import { BaseConverter } from "./../baseConverter";
+import {
+  Declaration,
+  ArrayDeclaration,
+  FunctionCall
+} from "../../ast/commands";
+import { Types } from "../../typeSystem/types";
+import { ArrayType } from "../../typeSystem/array_type";
+import {
+  InfixApp,
+  StringLiteral,
+  VariableLiteral,
+  UnaryApp,
+  BoolLiteral
+} from "../../ast/expressions";
+import { TextNode } from "./ast/textNode";
+import { Pointer } from "./types/pointer";
+import { Operators } from "../../ast/operators";
+import { LocalizedStrings } from "./../../services/localizedStringsService";
+import { LanguageDefinedFunction } from "../../processor/definedFunctions";
+
+const FORBIDDEN_FUNCS = [
+  "$isReal",
+  "$isInt",
+  "$isBool",
+  "$castReal",
+  "$castInt",
+  "$castBool",
+  "$castString",
+  "$max",
+  "$min",
+  "$invert"
+];
+
+export class CConverter extends BaseConverter {
+  constructor (ivprogAST) {
+    super(ivprogAST);
+    this.globalVars = [];
+    this.cAST = {
+      includes: new Set(),
+      globalVars: [],
+      functionDecl: [],
+      functions: []
+    };
+    this.currentVars = [];
+    this.currentCommands = [];
+    this.auxCounter = 1;
+  }
+
+  createAuxID () {
+    let id = null;
+    let symbol = true;
+    while (symbol) {
+      id = `aux_${this.auxCounter}`;
+      this.auxCounter += 1;
+      symbol = this.findSymbol(id);
+    }
+    return id;
+  }
+
+  resetContext () {
+    this.currentVars = [];
+    this.currentCommands = [];
+  }
+
+  toText () {
+    this.doConvertion();
+  }
+
+  doConversion () {
+    this.ivprogAST.global.forEach(decl => {
+      if (decl instanceof ArrayDeclaration) {
+        if (Types.STRING.isCompatible(decl.type.innerType)) {
+          throw new Error(
+            "Cannot convert a program that contains an array of text"
+          );
+        }
+      }
+      this.insertGlobalSymbol(decl.id, decl);
+
+      if (decl instanceof ArrayDeclaration) {
+        let initial = decl.initial;
+        if (initial) {
+          initial = this.convertArrayInit(initial);
+        }
+        const lines = this.convertExpression(decl.lines);
+        if (decl.isVector) {
+          this.cAST.globalVars.push(
+            new ArrayDeclaration(
+              decl.id,
+              decl.type,
+              lines,
+              null,
+              initial,
+              decl.isConst
+            )
+          );
+        } else {
+          const columns = this.convertExpression(decl.lines);
+          this.cAST.globalVars.push(
+            new ArrayDeclaration(
+              decl.id,
+              decl.type,
+              lines,
+              columns,
+              initial,
+              decl.isConst
+            )
+          );
+        }
+      } else {
+        if (decl.type.isCompatible(Types.STRING)) {
+          if (
+            decl.initial == null ||
+            !(decl.initial instanceof StringLiteral)
+          ) {
+            throw new Error(
+              "String must have a literal as its initial value to be converted to C"
+            );
+          }
+        }
+        let initial = decl.initial;
+        if (initial) {
+          initial = this.convertExpression(initial);
+        }
+        if (decl.type.isCompatible(Types.STRING)) {
+          // Must be a pointer since concatenation under the same symbol is expected (str = str + "abc")
+          this.cAST.globalVars.push(
+            new Declaration(
+              decl.id,
+              new Pointer(decl.type),
+              initial,
+              decl.isConst
+            )
+          );
+        } else {
+          this.cAST.globalVars.push(
+            new Declaration(decl.id, decl.type, initial, decl.isConst)
+          );
+        }
+      }
+    });
+    // convert every function...
+    for (let i = 0; i < this.ivprogAST.functions.length; i += 1) {
+      const func = this.ivprogAST.functions[i];
+      if (func.isMain) {
+        // deal with main function
+      } else {
+        // TODO
+      }
+    }
+  }
+
+  convertExpression (expression) {
+    const type = this.evaluateExpressionType(expression);
+    if (type instanceof ArrayType) {
+      // TODO
+    } else if (Types.STRING.isCompatible(type)) {
+      return this.convertStringExpression(expression);
+    } else if (Types.BOOLEAN.isCompatible(type)) {
+      return this.convertBooleanExpression(expression);
+    } else {
+      // It's a numeric expression
+      return this.convertNumericExpression(expression);
+    }
+  }
+
+  convertArrayInit (decl) {
+    return null;
+  }
+
+  convertNumericExpression (expression) {
+    if (expression instanceof UnaryApp) {
+      const leftExp = this.convertNumericExpression(expression.left);
+      return new TextNode(`${expression.op.value}${leftExp.toString()}`);
+    } else if (expression instanceof InfixApp) {
+      const leftExp = this.convertNumericExpression(expression.left);
+      const rightExp = this.convertNumericExpression(expression.right);
+      return new TextNode(
+        `${leftExp.toString()} ${expression.op.value} ${rightExp.toString()}`
+      );
+    } else if (expression instanceof FunctionCall) {
+      let funcName = expression.id;
+      const langFun = LanguageDefinedFunction.getFunction(funcName);
+      if (langFun) {
+        this.isValidFunction(funcName);
+        // function is valid, do we need to import any c lib?
+        // tan, cos, sen, sqrt, pow ...
+        const funcInfo = this.convertFunctionCall(funcName);
+        this.cAST.includes.add(funcInfo.lib);
+        funcName = funcInfo.name;
+      }
+      // need to verify every parameter...
+      const paramList = [];
+      for (let i = 0; i < expression.parametersSize; i += 1) {
+        const param = expression.actualParameters[i];
+        const paramExp = this.convertExpression(param);
+        paramList.push(paramExp.toString());
+      }
+      const paramString = paramList.join(", ");
+      return TextNode(`${funcName}(${paramString})`);
+    } else {
+      return expression;
+    }
+  }
+
+  convertBooleanExpression (expression) {
+    if (expression instanceof UnaryApp) {
+      const left = this.convertExpression(expression.left);
+      return new TextNode(`!${left.toString()}`);
+    } else if (expression instanceof InfixApp) {
+      const leftExp = this.convertExpression(expression.left);
+      const rightExp = this.convertExpression(expression.right);
+      const op = expression.op;
+      switch (op.ord) {
+        case Operators.AND.ord:
+          return new TextNode(
+            `${leftExp.toString()} && ${rightExp.toString()}`
+          );
+        case Operators.OR.ord:
+          return new TextNode(
+            `${leftExp.toString()} || ${rightExp.toString()}`
+          );
+        default:
+          return new TextNode(
+            `${leftExp.toString()} ${op.value} ${rightExp.toString()}`
+          );
+      }
+    } else if (expression instanceof BoolLiteral) {
+      this.cAST.includes.add("stdbool.h");
+      const value = expression.value ? "true" : "false";
+      return new TextNode(value);
+    } else if (expression instanceof FunctionCall) {
+      // Function call... if it's one of the convert/check functions DO NOT convert the code
+      let funcName = expression.id;
+      const langFun = LanguageDefinedFunction.getFunction(funcName);
+      if (langFun) {
+        this.isValidFunction(funcName);
+        const funcInfo = this.convertFunctionCall(funcName);
+        this.cAST.includes.add(funcInfo.lib);
+        funcName = funcInfo.name;
+      }
+
+      const paramList = [];
+      for (let i = 0; i < expression.parametersSize; i += 1) {
+        const param = expression.actualParameters[i];
+        const paramExp = this.convertExpression(param);
+        paramList.push(paramExp.toString());
+      }
+      const paramString = paramList.join(", ");
+      return TextNode(`${funcName}(${paramString})`);
+    } else {
+      return expression;
+    }
+  }
+
+  convertStringExpression (expression) {
+    if (expression instanceof InfixApp) {
+      const leftType = this.evaluateExpressionType(expression.left);
+      const rightType = this.evaluateExpressionType(expression.right);
+      if (
+        leftType.isCompatible(Types.STRING) &&
+        rightType.isCompatible(Types.STRING)
+      ) {
+        // BOTH strings....
+        // create a temp var with size equals to left + right + 1
+        const leftExp = this.convertStringExpression(expression.left);
+        const rightExp = this.convertStringExpression(expression.right);
+        this.currentCommands.push({
+          value:
+            "As 3 linhas seguintes convertem a expressão " +
+            expression.toString()
+        });
+        const auxID = this.createAuxID();
+        this.createCharPointer(auxID, true, leftExp, rightExp);
+        this.createCharCopy(auxID, leftExp);
+        this.createCharConcat(auxID, rightExp);
+        return new VariableLiteral(auxID);
+      } else if (leftType.isCompatible(Types.STRING)) {
+        // left is a string, right needs conversion
+        const leftExp = this.convertStringExpression(expression.left);
+        const rightExp = this.convertExpression(expression.right);
+        const formatString = this.getFormatStringFromType(rightType);
+        const auxConvert = this.createAuxID();
+        this.convertToString(auxConvert, formatString, rightExp);
+        const auxID = this.createAuxID();
+        this.createCharPointer(auxID, true, leftExp, auxConvert);
+        this.createCharCopy(auxID, leftExp);
+        this.createCharConcat(auxID, auxConvert);
+        return new VariableLiteral(auxID);
+      } else {
+        // right is a string, left needs conversion
+        const leftExp = this.convertExpression(expression.left);
+        const rightExp = this.convertStringExpression(expression.right);
+        const formatString = this.getFormatStringFromType(leftType);
+        const auxConvert = this.createAuxID();
+        this.convertToString(auxConvert, formatString, leftExp);
+        const auxID = this.createAuxID();
+        this.createCharPointer(auxID, true, auxConvert, rightExp);
+        this.createCharCopy(auxID, auxConvert);
+        this.createCharConcat(auxID, rightExp);
+        return new VariableLiteral(auxID);
+      }
+    } else if (expression instanceof StringLiteral) {
+      return expression;
+    } else if (expression instanceof FunctionCall) {
+      let funcName = expression.id;
+      const langFun = LanguageDefinedFunction.getFunction(funcName);
+      if (langFun) {
+        this.isValidFunction(funcName);
+        const funcInfo = this.convertFunctionCall(funcName);
+        this.cAST.includes.add(funcInfo.lib);
+        funcName = funcInfo.name;
+      }
+      const paramList = [];
+      for (let i = 0; i < expression.parametersSize; i += 1) {
+        const param = expression.actualParameters[i];
+        const paramExp = this.convertExpression(param);
+        paramList.push(paramExp.toString());
+      }
+      const paramString = paramList.join(", ");
+      return TextNode(`${funcName}(${paramString})`);
+    } else {
+      return expression;
+    }
+  }
+
+  getFormatStringFromType (type) {
+    switch (type.ord) {
+      case Types.INTEGER.ord:
+      case Types.BOOLEAN.ord:
+        return "%d";
+      case Types.STRING.ord:
+        return "%s";
+      default:
+        return "%g";
+    }
+  }
+
+  convertToString (id, format, expression) {
+    this.currentCommands.push({
+      value:
+        "As 3 linhas seguintes convertem a expressão " + expression.toString()
+    });
+    const lenID = this.createAuxID();
+    this.currentCommands.push(
+      new TextNode(
+        `int ${lenID} = snprintf(NULL, 0, ${format}, ${expression.toString()})`
+      )
+    );
+    this.createCharPointer(id, true, lenID);
+    this.currentCommands.push(
+      new TextNode(
+        `snprintf(${id}, ${lenID} + 1, ${format}, ${expression.toString()})`
+      )
+    );
+  }
+
+  createCharPointer (id, plusOne, ...args) {
+    this.cAST.includes.add("stdlib.h");
+    const funArgs = [];
+    for (let i = 0; i < args.length; i += 1) {
+      funArgs.push(`sizeof(${args[i].toString()})`);
+    }
+    if (plusOne) {
+      funArgs.push("1");
+    }
+    const params = funArgs.join(" + ");
+    const initial = new TextNode(`malloc(${params})`);
+    const decl = new Declaration(id, new Pointer(Types.STRING), initial);
+
+    this.currentCommands.push(decl);
+  }
+
+  createCharCopy (id, arg) {
+    this.cAST.includes.add("string.h");
+    const cmd = new TextNode(`strcpy(${id}, ${arg.toString()})`);
+    this.currentCommands.push(cmd);
+  }
+
+  createCharConcat (id, arg) {
+    this.cAST.includes.add("string.h");
+    const cmd = new TextNode(`strcat(${id}, ${arg.toString()})`);
+    this.currentCommands.push(cmd);
+  }
+
+  convertFunctionCall (name) {
+    let id = name;
+    if (name.indexOf(".") !== -1) {
+      const names = name.split(".");
+      id = names[1];
+    }
+    switch (id) {
+      case "$log":
+        return { lib: "math.h", name: "log10" };
+      case "$sin":
+      case "$cos":
+      case "$tan":
+      case "$sqrt":
+      case "$pow":
+        return { lib: "math.h", name: id.substring(1) };
+      case "$rand":
+        return { lib: "stdlib.h", name: id.substring(1) };
+      case "$abs":
+        return { lib: "stdlib.h", name: id.substring(1) };
+    }
+  }
+
+  isValidFunction (id) {
+    for (const name in FORBIDDEN_FUNCS) {
+      if (id.indexOf(name) !== -1) {
+        throw new Error(
+          `Não é possível converter um código que faz uso da função ${LocalizedStrings.translateInternalFunction(
+            name
+          )}.`
+        );
+      }
+    }
+  }
+}

+ 19 - 0
js/conversion/c/types/pointer.ts

@@ -0,0 +1,19 @@
+import { IType } from "../../../typeSystem/itype";
+import { Type } from "../../../typeSystem/type";
+
+export class Pointer implements IType {
+  baseType: Type;
+  value = undefined;
+  ord = undefined;
+
+  constructor (type: Type) {
+    this.baseType = type;
+  }
+
+  isCompatible (another: IType): boolean {
+    if (another instanceof Pointer) {
+      return this.baseType.isCompatible(another.baseType);
+    }
+    return false;
+  }
+}