From d6ccceb83de8622cdc9302d3cd126ec486b26c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Wed, 28 May 2025 00:33:53 +0200 Subject: [PATCH] AS1/2 variable highlighter improvements --- .../ActionScript2VariableParser.java | 146 ++++++++++++------ .../parser/script/variables/ClassScope.java | 43 ++++++ .../action/parser/script/variables/Type.java | 28 ++++ .../parser/script/variables/Variable.java | 5 + .../flash/gui/action/ActionPanel.java | 6 +- .../gui/action/ActionVariableMarker.java | 47 +++++- 6 files changed, 218 insertions(+), 57 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/ClassScope.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/Type.java diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/ActionScript2VariableParser.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/ActionScript2VariableParser.java index 77c8331f0..3dd3cc86c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/ActionScript2VariableParser.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/ActionScript2VariableParser.java @@ -46,7 +46,7 @@ public class ActionScript2VariableParser { * Swf version */ private final int swfVersion; - + /** * Constructor * @@ -57,7 +57,7 @@ public class ActionScript2VariableParser { } private final boolean debugMode = false; - + private void commands(boolean inFunction, boolean inMethod, int forinlevel, boolean inTellTarget, List variables, Reference hasEval) throws IOException, ActionParseException, InterruptedException { if (debugMode) { System.out.println("commands:"); @@ -67,22 +67,29 @@ public class ActionScript2VariableParser { } if (debugMode) { System.out.println("/commands"); - } + } } - private boolean type(List variables) throws IOException, ActionParseException, InterruptedException { + private String type(boolean definition, List variables) throws IOException, ActionParseException, InterruptedException { ParsedSymbol s = lex(); expectedIdentifier(s, lexer.yyline()); + ParsedSymbol lastIdent = s; Variable vret = new Variable(false, s.value.toString(), s.position); variables.add(vret); + String ret = s.value.toString(); s = lex(); while (s.type == SymbolType.DOT) { + ret += "."; s = lex(); expectedIdentifier(s, lexer.yyline()); + lastIdent = s; + ret += s.value.toString(); s = lex(); } lexer.pushback(s); - return true; + Type t = new Type(definition, ret, lastIdent.position); + variables.add(t); + return ret; } private void expected(ParsedSymbol symb, int line, Object... expected) throws IOException, ActionParseException { @@ -157,7 +164,7 @@ public class ActionScript2VariableParser { paramPositions.add(s.position); s = lex(); if (s.type == SymbolType.COLON) { - type(variables); + type(false, variables); s = lex(); } @@ -168,13 +175,16 @@ public class ActionScript2VariableParser { List subvariables = new ArrayList<>(); Reference subHasEval = new Reference<>(false); + if (!functionName.isEmpty()) { + variables.add(new Variable(true, functionName, functionNamePosition)); + } + for (int i = 0; i < paramNames.size(); i++) { subvariables.add(new Variable(true, paramNames.get(i), paramPositions.get(i))); } if (withBody) { expectedType(SymbolType.CURLY_OPEN); - //body = ; commands(true, isMethod, 0, inTellTarget, subvariables, subHasEval); expectedType(SymbolType.CURLY_CLOSE); } @@ -184,18 +194,20 @@ public class ActionScript2VariableParser { } variables.add(new FunctionScope(subvariables)); - if (!functionName.isEmpty()) { - variables.add(new Variable(true, functionName, functionNamePosition)); - } } - private boolean traits(boolean isInterface, List variables, boolean inTellTarget, Reference hasEval) throws IOException, ActionParseException, InterruptedException { + private boolean traits(boolean isInterface, String className, List variables, boolean inTellTarget, Reference hasEval) throws IOException, ActionParseException, InterruptedException { ParsedSymbol s; + looptrait: while (true) { + boolean isStatic = false; s = lex(); while (s.isType(SymbolType.STATIC, SymbolType.PUBLIC, SymbolType.PRIVATE)) { + if (s.type == SymbolType.STATIC) { + isStatic = true; + } s = lex(); } switch (s.type) { @@ -209,17 +221,18 @@ public class ActionScript2VariableParser { } expectedIdentifier(s, lexer.yyline()); + if (!isInterface) { - function(!isInterface, "", -1, true, variables, inTellTarget, hasEval); + function(!isInterface, isStatic ? className + "." + s.value.toString() : "this." + s.value.toString(), isStatic ? -1 : s.position, true, variables, inTellTarget, hasEval); } break; case VAR: s = lex(); expectedIdentifier(s, lexer.yyline()); - //String ident = s.value.toString(); + variables.add(new Variable(true, isStatic ? className + "." + s.value.toString() : "this." + s.value.toString(), s.position)); s = lex(); if (s.type == SymbolType.COLON) { - type(variables); + type(false, variables); s = lex(); } if (s.type == SymbolType.ASSIGN) { @@ -245,7 +258,7 @@ public class ActionScript2VariableParser { System.out.println("expressionCommands:"); } boolean ret; - + switch (s.type) { case DUPLICATEMOVIECLIP: case FSCOMMAND: @@ -293,7 +306,7 @@ public class ActionScript2VariableParser { variables.add(new Variable(false, (String) s.value, s.position)); break; } - + switch (s.type) { case DUPLICATEMOVIECLIP: expectedType(SymbolType.PARENT_OPEN); @@ -436,7 +449,7 @@ public class ActionScript2VariableParser { expression(inFunction, inMethod, inTellTarget, true, variables, false, hasEval); expectedType(SymbolType.COMMA); expression(inFunction, inMethod, inTellTarget, true, variables, false, hasEval); - expectedType(SymbolType.PARENT_CLOSE); + expectedType(SymbolType.PARENT_CLOSE); ret = true; break; case LOADVARIABLES: @@ -498,7 +511,7 @@ public class ActionScript2VariableParser { } } else { lexer.pushback(s); - + } } else { lexer.pushback(s); @@ -724,35 +737,39 @@ public class ActionScript2VariableParser { ret = true; break; case CLASS: - type(variables); + String className = type(true, variables); s = lex(); if (s.type == SymbolType.EXTENDS) { - type(variables); + type(false, variables); s = lex(); } if (s.type == SymbolType.IMPLEMENTS) { do { - type(variables); + type(false, variables); s = lex(); } while (s.type == SymbolType.COMMA); } expected(s, lexer.yyline(), SymbolType.CURLY_OPEN); - traits(false, variables, inTellTarget, hasEval); + List subVariables = new ArrayList<>(); + traits(false, className, subVariables, inTellTarget, hasEval); + ClassScope cs = new ClassScope(subVariables); + variables.add(cs); + expectedType(SymbolType.CURLY_CLOSE); ret = true; break; case INTERFACE: - type(variables); + String interfaceName = type(true, variables); s = lex(); if (s.type == SymbolType.EXTENDS) { do { - type(variables); + type(false, variables); s = lex(); } while (s.type == SymbolType.COMMA); } expected(s, lexer.yyline(), SymbolType.CURLY_OPEN); - traits(true, variables, inTellTarget, hasEval); + traits(true, interfaceName, variables, inTellTarget, hasEval); expectedType(SymbolType.CURLY_CLOSE); ret = true; break; @@ -768,7 +785,7 @@ public class ActionScript2VariableParser { int varPosition = s.position; s = lex(); if (s.type == SymbolType.COLON) { - type(variables); + type(false, variables); s = lex(); //TODO: handle value type } @@ -928,12 +945,12 @@ public class ActionScript2VariableParser { expectedIdentifier(si, lexer.yyline(), SymbolType.STRING); s = lex(); if (s.type == SymbolType.COLON) { - type(variables); + type(false, variables); } else { lexer.pushback(s); } expectedType(SymbolType.PARENT_CLOSE); - + List subvariables = new ArrayList<>(); command(inFunction, inMethod, forinlevel, inTellTarget, subvariables, hasEval); @@ -1124,7 +1141,7 @@ public class ActionScript2VariableParser { case ASSIGN_SHIFT_LEFT: case ASSIGN_SHIFT_RIGHT: case ASSIGN_USHIFT_RIGHT: - case ASSIGN_XOR: + case ASSIGN_XOR: lhs = true; break; case IDENTIFIER: @@ -1148,7 +1165,7 @@ public class ActionScript2VariableParser { } return lhs; } - + private int brackets(boolean inFunction, boolean inMethod, boolean inTellTarget, List variables, Reference hasEval) throws IOException, ActionParseException, InterruptedException { ParsedSymbol s = lex(); int arrCnt = 0; @@ -1183,6 +1200,38 @@ public class ActionScript2VariableParser { Variable vret = new Variable(false, varName, s.position); variables.add(vret); allowMemberOrCall.setVal(true); + + if (varName.equals("this")) { + ParsedSymbol s2 = lex(); + if (s2.type == SymbolType.DOT) { + ParsedSymbol s3 = lex(); + if (s3.group == SymbolGroup.IDENTIFIER) { + Variable thisVar = new Variable(false, "this." + s3.value.toString(), s3.position); + variables.add(thisVar); + } else { + lexer.pushback(s3); + lexer.pushback(s2); + } + } else { + lexer.pushback(s2); + } + } + ParsedSymbol ss = lex(); + String fullName = varName; + while (ss.type == SymbolType.DOT) { + ParsedSymbol si = lex(); + if (!isIdentifier(si)) { + lexer.pushback(si); + break; + } + fullName += "."; + fullName += si.value.toString(); + Variable v = new Variable(false, fullName, si.position); + variables.add(v); + ss = lex(); + } + lexer.pushback(ss); + ret = true; } return ret; @@ -1231,7 +1280,7 @@ public class ActionScript2VariableParser { case "goto": s = lexer.lex(); ret = true; - //throw new ActionParseException("Compiling §§" + s.value + " is not available, sorry", lexer.yyline()); + //throw new ActionParseException("Compiling §§" + s.value + " is not available, sorry", lexer.yyline()); default: throw new ActionParseException("Unknown preprocessor instruction: §§" + s.value, lexer.yyline()); @@ -1367,7 +1416,7 @@ public class ActionScript2VariableParser { expressionPrimary(inFunction, inMethod, inTellTarget, false, variables, false, hasEval); } expectedType(SymbolType.PARENT_OPEN); - call(inFunction, inMethod, inTellTarget, variables, hasEval); + call(inFunction, inMethod, inTellTarget, variables, hasEval); ret = true; allowMemberOrCall = true; @@ -1419,7 +1468,7 @@ public class ActionScript2VariableParser { } return ret; } - + private boolean memberOrCall(boolean ret, boolean inFunction, boolean inMethod, boolean inTellTarget, List variables, boolean allowCall, Reference hasEval) throws IOException, ActionParseException, InterruptedException { ParsedSymbol op = lex(); while (op.isType(SymbolType.PARENT_OPEN, SymbolType.BRACKET_OPEN, SymbolType.DOT)) { @@ -1427,7 +1476,7 @@ public class ActionScript2VariableParser { if (!allowCall) { break; } - call(inFunction, inMethod, inTellTarget, variables, hasEval); + call(inFunction, inMethod, inTellTarget, variables, hasEval); ret = true; } if (op.type == SymbolType.BRACKET_OPEN) { @@ -1551,7 +1600,7 @@ public class ActionScript2VariableParser { Map varNameToDefinitionPosition = new LinkedHashMap<>(); parseVariablesList(new ArrayList<>(), vars, definitionPosToReferences, referenceToDefinition, varNameToDefinitionPosition); - + if (inOnHandler) { expectedType(SymbolType.CURLY_CLOSE); } @@ -1578,14 +1627,16 @@ public class ActionScript2VariableParser { privateVarNameToDefinitionPosition.put(v.name, v.position); definitionPosToReferences.put(v.position, new ArrayList<>()); } else { - if (privateVarNameToDefinitionPosition.containsKey(v.name)) { + if (!privateVarNameToDefinitionPosition.containsKey(v.name)) { + parentVarNameToDefinitionPosition.put(v.name, -v.position - 1); + privateVarNameToDefinitionPosition.put(v.name, -v.position - 1); + definitionPosToReferences.put(-v.position - 1, new ArrayList<>()); + definitionPosToReferences.get(-v.position - 1).add(v.position); + referenceToDefinition.put(v.position, -v.position - 1); + } else { int definitionPos = privateVarNameToDefinitionPosition.get(v.name); definitionPosToReferences.get(definitionPos).add(v.position); referenceToDefinition.put(v.position, definitionPos); - } else { - //first usage, take as definition (?) - privateVarNameToDefinitionPosition.put(v.name, v.position); - definitionPosToReferences.put(v.position, new ArrayList<>()); } } } @@ -1602,15 +1653,16 @@ public class ActionScript2VariableParser { privateVarNameToDefinitionPosition.put(v.name, v.position); definitionPosToReferences.put(v.position, new ArrayList<>()); } else { - if (privateVarNameToDefinitionPosition.containsKey(v.name)) { + if (!privateVarNameToDefinitionPosition.containsKey(v.name)) { + parentVarNameToDefinitionPosition.put(v.name, -v.position - 1); + privateVarNameToDefinitionPosition.put(v.name, -v.position - 1); + definitionPosToReferences.put(-v.position - 1, new ArrayList<>()); + definitionPosToReferences.get(-v.position - 1).add(v.position); + referenceToDefinition.put(v.position, -v.position - 1); + } else { int definitionPos = privateVarNameToDefinitionPosition.get(v.name); definitionPosToReferences.get(definitionPos).add(v.position); referenceToDefinition.put(v.position, definitionPos); - } else { - //first usage, take as definition (?) - parentVarNameToDefinitionPosition.put(v.name, v.position); - privateVarNameToDefinitionPosition.put(v.name, v.position); - definitionPosToReferences.put(v.position, new ArrayList<>()); } } } @@ -1623,7 +1675,7 @@ public class ActionScript2VariableParser { private void versionRequired(ParsedSymbol s, int min) throws ActionParseException { versionRequired(s.value.toString(), min, Integer.MAX_VALUE); - } + } private void versionRequired(String type, int min, int max) throws ActionParseException { if (min == max && swfVersion != min) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/ClassScope.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/ClassScope.java new file mode 100644 index 000000000..0b935d394 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/ClassScope.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2010-2025 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.action.parser.script.variables; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class ClassScope implements Scope { + + private final List privateItems; + + public ClassScope(List traits) { + this.privateItems = traits; + } + + @Override + public List getSharedItems() { + return new ArrayList<>(); + } + + @Override + public List getPrivateItems() { + return privateItems; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/Type.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/Type.java new file mode 100644 index 000000000..88279c65c --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/Type.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2010-2025 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.action.parser.script.variables; + +/** + * + * @author JPEXS + */ +public class Type extends Variable { + + public Type(boolean definition, String name, int position) { + super(definition, name, position); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/Variable.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/Variable.java index d4eceb3c1..eceb65cf6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/Variable.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/variables/Variable.java @@ -31,4 +31,9 @@ public class Variable implements VariableOrScope { this.name = name; this.position = position; } + + @Override + public String toString() { + return (definition ? "definition of " : "") + name + " at " + position; + } } diff --git a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java index 779b0222a..5145ee6ce 100644 --- a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java @@ -712,7 +712,7 @@ public class ActionPanel extends JPanel implements SearchListener(new FlowLayout(), this); @@ -1409,7 +1409,7 @@ public class ActionPanel extends JPanel implements SearchListener= 0; + } + + @Override + public void handleLink(Token token) { + Integer definition = referenceToDefinition.get(token.start); + if (definition != null) { + pane.setCaretPosition(definition); + } + } + + @Override + public Highlighter.HighlightPainter linkPainter() { + return ((LineMarkedEditorPane) pane).linkPainter(); + } }