Mercerenies / gdlisp

Lisp on the Godot platform
GNU General Public License v3.0
145 stars 1 forks source link

Redundant assignment elimination may transform the variable beyond its scope #152

Open starsJuly opened 4 months ago

starsJuly commented 4 months ago

When trying to write the Dodge the Creeps (Godot 2d demo) with GDLisp, the GDLisp compiler outputs the script error :

GDLisp v1.0.0
Running under Godot
3.5.3.stable.official.6c814135b
Compiling .\main.lisp ...
SCRIPT ERROR: Parse Error: The identifier "direction" isn't declared in the current scope.
          at: GDScript::reload (C:/Users/yzxl/AppData/Local/Temp/__gdlisp_fileDt1rg.gd:38)

The same parsed error can be caused by the simple statement :

(let ((x 0))
      (set x (+ x 1)))

The statement will be transformed by the GDLisp compiler, and output some GDScript statements as below:

func test():
    var x = x + 1
    return x

After a several attempts, I think that the redundant assignment elimination leads to the variable beyond its scope. See the src\optimize\gdscript\redundant_assignment_elimination.rs. :)

  // ...
  pub fn run_on_stmts(&self, stmts: &[Stmt]) -> Result<Vec<Stmt>, GDError> {
    // Look for something to eliminate. If we find it, do the
    // elimination and call the function again. If not, we're done.
    for (index, stmt1) in stmts.iter().enumerate() {
      if let Some(AssignmentStmt { assign_type, var_name: name, expr: expr1 }) = self.match_first_assign(stmt1) {
        if !constant::expr_has_side_effects(expr1) {
          // Found a match. Keep going.
          for jndex in index+1..stmts.len() {
            let stmt2 = &stmts[jndex];
            if let Some(expr2) = self.match_second_assign(name, stmt2) {
              // Redundant assignment; cull
              let new_stmt = self.rebuild_assignment(assign_type.ensure_eq(), name, expr2);
              let mut new_stmts = stmts.to_vec();
              new_stmts.splice(index..=jndex, vec!(new_stmt).into_iter());
              return self.run_on_stmts(&new_stmts);
            } else if constant::stmt_has_side_effects(stmt2) {
              // If it's stateful, then we can't do anything
              break;
            }
          }
        }
      }
    }
  //...

When the first assignment type is VarDecl and the second assignment type is AssignType::Assignment(op::AssignOp::Eq), the expression of the second statement will be moved as the new VarDecl statement expression. The expression may contains any number of references of the variable (with the same name).

When inserting some stateful statements, or replacing the second assignment with other compound assignments, the script error will disappear. For example,

(let ((x 0))
      (print "hello world")
      (set x (+ x 1)))

Of course, they not trigger the redundant assignment elimination. :)

Environment Info.
Windows
Godot 3.5.3.stable.official.6c814135b
GDLisp v1.0.0