/* * Copyright (C) 2010-2016 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; import com.jpexs.decompiler.flash.SourceGeneratorLocalData; import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.model.AsciiToCharActionItem; import com.jpexs.decompiler.flash.action.model.CallActionItem; import com.jpexs.decompiler.flash.action.model.CallFunctionActionItem; import com.jpexs.decompiler.flash.action.model.CallMethodActionItem; import com.jpexs.decompiler.flash.action.model.CastOpActionItem; import com.jpexs.decompiler.flash.action.model.CharToAsciiActionItem; import com.jpexs.decompiler.flash.action.model.CloneSpriteActionItem; import com.jpexs.decompiler.flash.action.model.DefineLocalActionItem; import com.jpexs.decompiler.flash.action.model.DeleteActionItem; import com.jpexs.decompiler.flash.action.model.DirectValueActionItem; import com.jpexs.decompiler.flash.action.model.EnumerateActionItem; import com.jpexs.decompiler.flash.action.model.EvalActionItem; import com.jpexs.decompiler.flash.action.model.FSCommandActionItem; import com.jpexs.decompiler.flash.action.model.FunctionActionItem; import com.jpexs.decompiler.flash.action.model.GetMemberActionItem; import com.jpexs.decompiler.flash.action.model.GetTimeActionItem; import com.jpexs.decompiler.flash.action.model.GetURL2ActionItem; import com.jpexs.decompiler.flash.action.model.GetVariableActionItem; import com.jpexs.decompiler.flash.action.model.GetVersionActionItem; import com.jpexs.decompiler.flash.action.model.GotoFrame2ActionItem; import com.jpexs.decompiler.flash.action.model.InitArrayActionItem; import com.jpexs.decompiler.flash.action.model.InitObjectActionItem; import com.jpexs.decompiler.flash.action.model.LoadMovieActionItem; import com.jpexs.decompiler.flash.action.model.LoadMovieNumActionItem; import com.jpexs.decompiler.flash.action.model.LoadVariablesActionItem; import com.jpexs.decompiler.flash.action.model.LoadVariablesNumActionItem; import com.jpexs.decompiler.flash.action.model.MBAsciiToCharActionItem; import com.jpexs.decompiler.flash.action.model.MBCharToAsciiActionItem; import com.jpexs.decompiler.flash.action.model.MBStringExtractActionItem; import com.jpexs.decompiler.flash.action.model.MBStringLengthActionItem; import com.jpexs.decompiler.flash.action.model.NewMethodActionItem; import com.jpexs.decompiler.flash.action.model.NewObjectActionItem; import com.jpexs.decompiler.flash.action.model.NextFrameActionItem; import com.jpexs.decompiler.flash.action.model.PlayActionItem; import com.jpexs.decompiler.flash.action.model.PostDecrementActionItem; import com.jpexs.decompiler.flash.action.model.PostIncrementActionItem; import com.jpexs.decompiler.flash.action.model.PrevFrameActionItem; import com.jpexs.decompiler.flash.action.model.PrintActionItem; import com.jpexs.decompiler.flash.action.model.PrintAsBitmapActionItem; import com.jpexs.decompiler.flash.action.model.PrintAsBitmapNumActionItem; import com.jpexs.decompiler.flash.action.model.PrintNumActionItem; import com.jpexs.decompiler.flash.action.model.RandomNumberActionItem; import com.jpexs.decompiler.flash.action.model.RemoveSpriteActionItem; import com.jpexs.decompiler.flash.action.model.ReturnActionItem; import com.jpexs.decompiler.flash.action.model.SetMemberActionItem; import com.jpexs.decompiler.flash.action.model.SetVariableActionItem; import com.jpexs.decompiler.flash.action.model.StartDragActionItem; import com.jpexs.decompiler.flash.action.model.StopActionItem; import com.jpexs.decompiler.flash.action.model.StopAllSoundsActionItem; import com.jpexs.decompiler.flash.action.model.StopDragActionItem; import com.jpexs.decompiler.flash.action.model.StringExtractActionItem; import com.jpexs.decompiler.flash.action.model.StringLengthActionItem; import com.jpexs.decompiler.flash.action.model.TargetPathActionItem; import com.jpexs.decompiler.flash.action.model.ThrowActionItem; import com.jpexs.decompiler.flash.action.model.ToIntegerActionItem; import com.jpexs.decompiler.flash.action.model.ToNumberActionItem; import com.jpexs.decompiler.flash.action.model.ToStringActionItem; import com.jpexs.decompiler.flash.action.model.ToggleHighQualityActionItem; import com.jpexs.decompiler.flash.action.model.TraceActionItem; import com.jpexs.decompiler.flash.action.model.TypeOfActionItem; import com.jpexs.decompiler.flash.action.model.UnLoadMovieActionItem; import com.jpexs.decompiler.flash.action.model.UnLoadMovieNumActionItem; import com.jpexs.decompiler.flash.action.model.clauses.ClassActionItem; import com.jpexs.decompiler.flash.action.model.clauses.ForInActionItem; import com.jpexs.decompiler.flash.action.model.clauses.IfFrameLoadedActionItem; import com.jpexs.decompiler.flash.action.model.clauses.InterfaceActionItem; import com.jpexs.decompiler.flash.action.model.clauses.TellTargetActionItem; import com.jpexs.decompiler.flash.action.model.clauses.TryActionItem; import com.jpexs.decompiler.flash.action.model.clauses.WithActionItem; import com.jpexs.decompiler.flash.action.model.operations.AddActionItem; import com.jpexs.decompiler.flash.action.model.operations.AndActionItem; import com.jpexs.decompiler.flash.action.model.operations.BitAndActionItem; import com.jpexs.decompiler.flash.action.model.operations.BitOrActionItem; import com.jpexs.decompiler.flash.action.model.operations.BitXorActionItem; import com.jpexs.decompiler.flash.action.model.operations.DivideActionItem; import com.jpexs.decompiler.flash.action.model.operations.EqActionItem; import com.jpexs.decompiler.flash.action.model.operations.GeActionItem; import com.jpexs.decompiler.flash.action.model.operations.GtActionItem; import com.jpexs.decompiler.flash.action.model.operations.InstanceOfActionItem; import com.jpexs.decompiler.flash.action.model.operations.LShiftActionItem; import com.jpexs.decompiler.flash.action.model.operations.LeActionItem; import com.jpexs.decompiler.flash.action.model.operations.LtActionItem; import com.jpexs.decompiler.flash.action.model.operations.ModuloActionItem; import com.jpexs.decompiler.flash.action.model.operations.MultiplyActionItem; import com.jpexs.decompiler.flash.action.model.operations.NeqActionItem; import com.jpexs.decompiler.flash.action.model.operations.OrActionItem; import com.jpexs.decompiler.flash.action.model.operations.PreDecrementActionItem; import com.jpexs.decompiler.flash.action.model.operations.PreIncrementActionItem; import com.jpexs.decompiler.flash.action.model.operations.RShiftActionItem; import com.jpexs.decompiler.flash.action.model.operations.StrictEqActionItem; import com.jpexs.decompiler.flash.action.model.operations.StrictNeqActionItem; import com.jpexs.decompiler.flash.action.model.operations.StringAddActionItem; import com.jpexs.decompiler.flash.action.model.operations.StringEqActionItem; import com.jpexs.decompiler.flash.action.model.operations.StringGeActionItem; import com.jpexs.decompiler.flash.action.model.operations.StringGtActionItem; import com.jpexs.decompiler.flash.action.model.operations.StringLeActionItem; import com.jpexs.decompiler.flash.action.model.operations.StringLtActionItem; import com.jpexs.decompiler.flash.action.model.operations.StringNeActionItem; import com.jpexs.decompiler.flash.action.model.operations.SubtractActionItem; import com.jpexs.decompiler.flash.action.model.operations.URShiftActionItem; import com.jpexs.decompiler.flash.action.parser.ActionParseException; import com.jpexs.decompiler.flash.action.swf4.ActionIf; import com.jpexs.decompiler.flash.action.swf4.ConstantIndex; import com.jpexs.decompiler.flash.action.swf5.ActionConstantPool; import com.jpexs.decompiler.flash.ecma.Null; import com.jpexs.decompiler.flash.ecma.Undefined; import com.jpexs.decompiler.flash.helpers.GraphTextWriter; import com.jpexs.decompiler.flash.helpers.collections.MyEntry; import com.jpexs.decompiler.graph.CompilationException; import com.jpexs.decompiler.graph.GraphSourceItem; import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.decompiler.graph.TypeItem; import com.jpexs.decompiler.graph.model.AndItem; import com.jpexs.decompiler.graph.model.BlockItem; import com.jpexs.decompiler.graph.model.BreakItem; import com.jpexs.decompiler.graph.model.CommaExpressionItem; import com.jpexs.decompiler.graph.model.ContinueItem; import com.jpexs.decompiler.graph.model.DefaultItem; import com.jpexs.decompiler.graph.model.DoWhileItem; import com.jpexs.decompiler.graph.model.DuplicateItem; import com.jpexs.decompiler.graph.model.ForItem; import com.jpexs.decompiler.graph.model.IfItem; import com.jpexs.decompiler.graph.model.LocalData; import com.jpexs.decompiler.graph.model.NotItem; import com.jpexs.decompiler.graph.model.OrItem; import com.jpexs.decompiler.graph.model.ParenthesisItem; import com.jpexs.decompiler.graph.model.PopItem; import com.jpexs.decompiler.graph.model.PushItem; import com.jpexs.decompiler.graph.model.SwitchItem; import com.jpexs.decompiler.graph.model.TernarOpItem; import com.jpexs.decompiler.graph.model.WhileItem; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; /** * * @author JPEXS */ public class ActionScript2Parser { private final int swfVersion; public ActionScript2Parser(int swfVersion) { this.swfVersion = swfVersion; } private long uniqLast = 0; private final boolean debugMode = false; private String uniqId() { uniqLast++; return "" + uniqLast; } private List<GraphTargetItem> commands(boolean inFunction, boolean inMethod, int forinlevel, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { List<GraphTargetItem> ret = new ArrayList<>(); if (debugMode) { System.out.println("commands:"); } GraphTargetItem cmd; while ((cmd = command(inFunction, inMethod, forinlevel, true, variables, functions)) != null) { ret.add(cmd); } if (debugMode) { System.out.println("/commands"); } return ret; } private GraphTargetItem type(List<VariableActionItem> variables) throws IOException, ActionParseException { GraphTargetItem ret; ParsedSymbol s = lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER, SymbolType.STRING_OP); ret = new VariableActionItem(s.value.toString(), null, false); variables.add((VariableActionItem) ret); s = lex(); while (s.type == SymbolType.DOT) { s = lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER, SymbolType.STRING_OP, SymbolGroup.GLOBALFUNC); ret = new GetMemberActionItem(null, null, ret, pushConst(s.value.toString())); s = lex(); } lexer.pushback(s); return ret; } /*private GraphTargetItem variable(boolean inFunction, boolean inMethod, List<VariableActionItem> variables) throws IOException, ActionParseException { GraphTargetItem ret = null; ParsedSymbol s = lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER, SymbolType.THIS, SymbolType.SUPER, SymbolType.STRING_OP); ret = new VariableActionItem(s.value.toString(), null, false); variables.add((VariableActionItem) ret); ret = (member(ret, inFunction, inMethod, variables, functions)); return ret; }*/ private void expected(ParsedSymbol symb, int line, Object... expected) throws IOException, ActionParseException { boolean found = false; for (Object t : expected) { if (symb.type == t) { found = true; } if (symb.group == t) { found = true; } } if (!found) { String expStr = ""; boolean first = true; for (Object e : expected) { if (!first) { expStr += " or "; } expStr += e; first = false; } throw new ActionParseException("" + expStr + " expected but " + symb.type + " found", line); } } private ParsedSymbol expectedType(Object... type) throws IOException, ActionParseException { ParsedSymbol symb = lex(); expected(symb, lexer.yyline(), type); return symb; } private ParsedSymbol lex() throws IOException, ActionParseException { ParsedSymbol ret = lexer.lex(); if (debugMode) { System.out.println(ret); } return ret; } private List<GraphTargetItem> call(boolean inFunction, boolean inMethod, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { List<GraphTargetItem> ret = new ArrayList<>(); //expected(SymbolType.PARENT_OPEN); //MUST BE HANDLED BY CALLER ParsedSymbol s = lex(); while (s.type != SymbolType.PARENT_CLOSE) { if (s.type != SymbolType.COMMA) { lexer.pushback(s); } ret.add(expression(inFunction, inMethod, true, variables, functions)); s = lex(); expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE); } return ret; } private FunctionActionItem function(boolean withBody, String functionName, boolean isMethod, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { GraphTargetItem ret = null; ParsedSymbol s; expectedType(SymbolType.PARENT_OPEN); s = lex(); List<String> paramNames = new ArrayList<>(); while (s.type != SymbolType.PARENT_CLOSE) { if (s.type != SymbolType.COMMA) { lexer.pushback(s); } s = lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER); paramNames.add(s.value.toString()); s = lex(); if (s.type == SymbolType.COLON) { type(variables); s = lex(); } if (!s.isType(SymbolType.COMMA, SymbolType.PARENT_CLOSE)) { expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE); } } List<GraphTargetItem> body = null; List<VariableActionItem> subvariables = new ArrayList<>(); List<FunctionActionItem> subfunctions = new ArrayList<>(); if (withBody) { expectedType(SymbolType.CURLY_OPEN); body = commands(true, isMethod, 0, subvariables, subfunctions); expectedType(SymbolType.CURLY_CLOSE); } FunctionActionItem retf = new FunctionActionItem(null, null, functionName, paramNames, new HashMap<>() /*?*/, body, constantPool, -1, subvariables, subfunctions); functions.add(retf); return retf; } private GraphTargetItem traits(boolean isInterface, GraphTargetItem nameStr, GraphTargetItem extendsStr, List<GraphTargetItem> implementsStr, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { GraphTargetItem ret = null; /*for (int i = 0; i < nameStr.size() - 1; i++) { List<GraphTargetItem> notBody = new ArrayList<>(); List<String> globalClassTypeStr = new ArrayList<>(); globalClassTypeStr.add("_global"); for (int j = 0; j <= i; j++) { globalClassTypeStr.add(nameStr.get(j)); } List<GraphTargetItem> val = new ArrayList<>(); val.add(new ActionPush((Long) 0L)); val.add(pushConst("Object")); val.add(new ActionNewObject()); notBody.addAll(typeToActions(globalClassTypeStr, val)); ret.addAll(typeToActions(globalClassTypeStr, null)); ret.add(new ActionNot()); ret.add(new ActionNot()); ret.add(new ActionIf(GraphTargetItem.actionsToBytes(notBody, false, SWF.DEFAULT_VERSION).length)); ret.addAll(notBody); } List<GraphTargetItem> ifbody = new ArrayList<>(); List<String> globalClassTypeStr = new ArrayList<>(); globalClassTypeStr.add("_global"); globalClassTypeStr.addAll(nameStr);*/ ParsedSymbol s; FunctionActionItem constr = null; List<GraphTargetItem> staticFunctions = new ArrayList<>(); List<MyEntry<GraphTargetItem, GraphTargetItem>> staticVars = new ArrayList<>(); List<GraphTargetItem> instanceFunctions = new ArrayList<>(); List<MyEntry<GraphTargetItem, GraphTargetItem>> vars = new ArrayList<>(); String classNameStr = ""; if (nameStr instanceof GetMemberActionItem) { GetMemberActionItem mem = (GetMemberActionItem) nameStr; if (mem.memberName instanceof VariableActionItem) { classNameStr = ((VariableActionItem) mem.memberName).getVariableName(); } else if (mem.memberName instanceof DirectValueActionItem) { classNameStr = ((DirectValueActionItem) mem.memberName).toStringNoQuotes(LocalData.empty); } } else if (nameStr instanceof VariableActionItem) { VariableActionItem var = (VariableActionItem) nameStr; classNameStr = var.getVariableName(); } looptrait: while (true) { s = lex(); boolean isStatic = false; while (s.isType(SymbolType.STATIC, SymbolType.PUBLIC, SymbolType.PRIVATE)) { if (s.type == SymbolType.STATIC) { isStatic = true; } s = lex(); } switch (s.type) { case FUNCTION: s = lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER, SymbolGroup.GLOBALFUNC); String fname = s.value.toString(); if (fname.equals(classNameStr)) { //constructor constr = (function(!isInterface, "", true, variables, functions)); } else if (!isInterface) { if (isStatic) { FunctionActionItem ft = function(!isInterface, "", true, variables, functions); ft.calculatedFunctionName = pushConst(fname); staticFunctions.add(ft); } else { FunctionActionItem ft = function(!isInterface, "", true, variables, functions); ft.calculatedFunctionName = pushConst(fname); instanceFunctions.add(ft); } } break; case VAR: s = lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER); String ident = s.value.toString(); s = lex(); if (s.type == SymbolType.COLON) { type(variables); s = lex(); } if (s.type == SymbolType.ASSIGN) { if (isStatic) { staticVars.add(new MyEntry<>(pushConst(ident), expression(false, false, true, variables, functions))); } else { vars.add(new MyEntry<>(pushConst(ident), expression(false, false, true, variables, functions))); } s = lex(); } if (s.type != SymbolType.SEMICOLON) { lexer.pushback(s); } break; default: lexer.pushback(s); break looptrait; } } if (isInterface) { return new InterfaceActionItem(nameStr, implementsStr); } else { return new ClassActionItem(nameStr, extendsStr, implementsStr, constr, instanceFunctions, vars, staticFunctions, staticVars); } } private GraphTargetItem expressionCommands(ParsedSymbol s, boolean inFunction, boolean inMethod, int forinlevel, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { if (debugMode) { System.out.println("expressionCommands:"); } GraphTargetItem ret; switch (s.type) { case GETVERSION: expectedType(SymbolType.PARENT_OPEN); ret = new GetVersionActionItem(null, null); expectedType(SymbolType.PARENT_CLOSE); break; case MBORD: expectedType(SymbolType.PARENT_OPEN); ret = new MBCharToAsciiActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); break; case MBCHR: expectedType(SymbolType.PARENT_OPEN); ret = new MBAsciiToCharActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); break; case MBLENGTH: expectedType(SymbolType.PARENT_OPEN); ret = new MBStringLengthActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); break; case MBSUBSTRING: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem val1 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.COMMA); GraphTargetItem index1 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.COMMA); GraphTargetItem len1 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); ret = new MBStringExtractActionItem(null, null, val1, index1, len1); break; case SUBSTR: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem val2 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.COMMA); GraphTargetItem index2 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.COMMA); GraphTargetItem len2 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); ret = new StringExtractActionItem(null, null, val2, index2, len2); break; case LENGTH: expectedType(SymbolType.PARENT_OPEN); ret = new StringLengthActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); break; case RANDOM: expectedType(SymbolType.PARENT_OPEN); ret = new RandomNumberActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); break; case INT: expectedType(SymbolType.PARENT_OPEN); ret = new ToIntegerActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); break; case NUMBER_OP: ParsedSymbol sopn = s; s = lex(); if (s.type == SymbolType.DOT) { lexer.pushback(s); VariableActionItem vi = new VariableActionItem(sopn.value.toString(), null, false); variables.add(vi); ret = vi; //memberOrCall(vi, inFunction, inMethod, variables, functions); } else { expected(s, lexer.yyline(), SymbolType.PARENT_OPEN); ret = new ToNumberActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); } break; case STRING_OP: ParsedSymbol sop = s; s = lex(); if (s.type == SymbolType.DOT) { lexer.pushback(s); VariableActionItem vi2 = new VariableActionItem(sop.value.toString(), null, false); variables.add(vi2); ret = vi2; //memberOrCall(vi2, inFunction, inMethod, variables, functions); } else { expected(s, lexer.yyline(), SymbolType.PARENT_OPEN); ret = new ToStringActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); //ret = memberOrCall(ret, inFunction, inMethod, variables, functions); } break; case ORD: expectedType(SymbolType.PARENT_OPEN); ret = new CharToAsciiActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); break; case CHR: expectedType(SymbolType.PARENT_OPEN); ret = new AsciiToCharActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); break; case DUPLICATEMOVIECLIP: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem src3 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.COMMA); GraphTargetItem tar3 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.COMMA); GraphTargetItem dep3 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); ret = new CloneSpriteActionItem(null, null, src3, tar3, dep3); break; case GETTIMER: expectedType(SymbolType.PARENT_OPEN); expectedType(SymbolType.PARENT_CLOSE); ret = new GetTimeActionItem(null, null); break; default: return null; } if (debugMode) { System.out.println("/expressionCommands"); } return ret; } private GraphTargetItem command(boolean inFunction, boolean inMethod, int forinlevel, boolean mustBeCommand, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { LexBufferer buf = new LexBufferer(); lexer.addListener(buf); GraphTargetItem ret = null; if (debugMode) { System.out.println("command:"); } ParsedSymbol s = lex(); if (s.type == SymbolType.EOF) { return null; } switch (s.type) { case FSCOMMAND: expectedType(SymbolType.PARENT_OPEN); ret = new FSCommandActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); break; case CALL: expectedType(SymbolType.PARENT_OPEN); ret = new CallActionItem(null, null, (expression(inFunction, inMethod, true, variables, functions))); expectedType(SymbolType.PARENT_CLOSE); break; case LENGTH: expectedType(SymbolType.PARENT_OPEN); ret = new StringLengthActionItem(null, null, (expression(inFunction, inMethod, true, variables, functions))); expectedType(SymbolType.PARENT_CLOSE); break; case MBLENGTH: expectedType(SymbolType.PARENT_OPEN); ret = new MBStringLengthActionItem(null, null, (expression(inFunction, inMethod, true, variables, functions))); expectedType(SymbolType.PARENT_CLOSE); break; case SET: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem name1 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.COMMA); GraphTargetItem value1 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); ret = new SetVariableActionItem(null, null, name1, value1); break; case WITH: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem wvar = expression(inFunction, inMethod, false, variables, functions);//(variable(inFunction, inMethod, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); expectedType(SymbolType.CURLY_OPEN); List<GraphTargetItem> wcmd = commands(inFunction, inMethod, forinlevel, variables, functions); expectedType(SymbolType.CURLY_CLOSE); ret = new WithActionItem(null, null, wvar, wcmd); break; case DELETE: GraphTargetItem varDel = expression(inFunction, inMethod, false, variables, functions);//variable(inFunction, inMethod, variables, functions); if (varDel instanceof GetMemberActionItem) { GetMemberActionItem gm = (GetMemberActionItem) varDel; ret = new DeleteActionItem(null, null, gm.object, gm.memberName); } else if (varDel instanceof VariableActionItem) { variables.remove(varDel); ret = new DeleteActionItem(null, null, null, pushConst(((VariableActionItem) varDel).getVariableName())); } else { throw new ActionParseException("Not a property", lexer.yyline()); } break; case TRACE: expectedType(SymbolType.PARENT_OPEN); ret = new TraceActionItem(null, null, (expression(inFunction, inMethod, true, variables, functions))); expectedType(SymbolType.PARENT_CLOSE); break; case GETURL: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem url = (expression(inFunction, inMethod, true, variables, functions)); s = lex(); expected(s, lexer.yyline(), SymbolType.PARENT_CLOSE, SymbolType.COMMA); int getuMethod = 1; GraphTargetItem target; if (s.type == SymbolType.COMMA) { target = (expression(inFunction, inMethod, true, variables, functions)); s = lex(); if (s.type == SymbolType.COMMA) { s = lex(); expected(s, lexer.yyline(), SymbolType.STRING); if (s.value.equals("GET")) { getuMethod = 1; } else if (s.value.equals("POST")) { getuMethod = 2; } else { throw new ActionParseException("Invalid method, \"GET\" or \"POST\" expected.", lexer.yyline()); } } else { lexer.pushback(s); } } else { lexer.pushback(s); target = new DirectValueActionItem(null, null, 0, "", new ArrayList<>()); } expectedType(SymbolType.PARENT_CLOSE); ret = new GetURL2ActionItem(null, null, url, target, getuMethod); break; case GOTOANDSTOP: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem gtsFrame = expression(inFunction, inMethod, true, variables, functions); s = lex(); if (s.type == SymbolType.COMMA) { //Handle scene? lex(); gtsFrame = expression(inFunction, inMethod, true, variables, functions); } else { lexer.pushback(s); } ret = new GotoFrame2ActionItem(null, null, gtsFrame, false, false, 0); expectedType(SymbolType.PARENT_CLOSE); break; case NEXTFRAME: expectedType(SymbolType.PARENT_OPEN); expectedType(SymbolType.PARENT_CLOSE); ret = new NextFrameActionItem(null, null); break; case PLAY: expectedType(SymbolType.PARENT_OPEN); expectedType(SymbolType.PARENT_CLOSE); ret = new PlayActionItem(null, null); break; case PREVFRAME: expectedType(SymbolType.PARENT_OPEN); expectedType(SymbolType.PARENT_CLOSE); ret = new PrevFrameActionItem(null, null); break; case TELLTARGET: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem tellTarget = expression(inFunction, inMethod, true, variables, functions); expectedType(SymbolType.PARENT_CLOSE); expectedType(SymbolType.CURLY_OPEN); List<GraphTargetItem> tellcmds = commands(inFunction, inMethod, forinlevel, variables, functions); expectedType(SymbolType.CURLY_CLOSE); ret = new TellTargetActionItem(null, null, tellTarget, tellcmds); break; case STOP: expectedType(SymbolType.PARENT_OPEN); expectedType(SymbolType.PARENT_CLOSE); ret = new StopActionItem(null, null); break; case STOPALLSOUNDS: expectedType(SymbolType.PARENT_OPEN); expectedType(SymbolType.PARENT_CLOSE); ret = new StopAllSoundsActionItem(null, null); break; case TOGGLEHIGHQUALITY: expectedType(SymbolType.PARENT_OPEN); expectedType(SymbolType.PARENT_CLOSE); ret = new ToggleHighQualityActionItem(null, null); break; case STOPDRAG: expectedType(SymbolType.PARENT_OPEN); expectedType(SymbolType.PARENT_CLOSE); ret = new StopDragActionItem(null, null); break; case TARGETPATH: expectedType(SymbolType.PARENT_OPEN); ret = new TargetPathActionItem(null, null, (expression(inFunction, inMethod, true, variables, functions))); expectedType(SymbolType.PARENT_CLOSE); break; case UNLOADMOVIE: case UNLOADMOVIENUM: SymbolType unloadType = s.type; expectedType(SymbolType.PARENT_OPEN); GraphTargetItem unTargetOrNum = expression(inFunction, inMethod, true, variables, functions); expectedType(SymbolType.PARENT_CLOSE); if (unloadType == SymbolType.UNLOADMOVIE) { ret = new UnLoadMovieActionItem(null, null, unTargetOrNum); } if (unloadType == SymbolType.UNLOADMOVIENUM) { ret = new UnLoadMovieNumActionItem(null, null, unTargetOrNum); } break; case PRINT: case PRINTASBITMAP: case PRINTASBITMAPNUM: case PRINTNUM: SymbolType printType = s.type; expectedType(SymbolType.PARENT_OPEN); GraphTargetItem printTarget = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.COMMA); GraphTargetItem printBBox = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); switch (printType) { case PRINT: ret = new PrintActionItem(null, null, printTarget, printBBox); break; case PRINTNUM: ret = new PrintNumActionItem(null, null, printTarget, printBBox); break; case PRINTASBITMAP: ret = new PrintAsBitmapActionItem(null, null, printTarget, printBBox); break; case PRINTASBITMAPNUM: ret = new PrintAsBitmapNumActionItem(null, null, printTarget, printBBox); break; } break; case LOADVARIABLES: case LOADMOVIE: case LOADVARIABLESNUM: case LOADMOVIENUM: SymbolType loadType = s.type; expectedType(SymbolType.PARENT_OPEN); GraphTargetItem url2 = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.COMMA); GraphTargetItem targetOrNum = (expression(inFunction, inMethod, true, variables, functions)); s = lex(); expected(s, lexer.yyline(), SymbolType.PARENT_CLOSE, SymbolType.COMMA); int lvmethod = 1; if (s.type == SymbolType.COMMA) { s = lex(); expected(s, lexer.yyline(), SymbolType.STRING); if (s.value.equals("POST")) { lvmethod = 2; } else if (s.value.equals("GET")) { lvmethod = 1; } else { throw new ActionParseException("Invalid method, \"GET\" or \"POST\" expected.", lexer.yyline()); } } else { lexer.pushback(s); } expectedType(SymbolType.PARENT_CLOSE); switch (loadType) { case LOADVARIABLES: ret = new LoadVariablesActionItem(null, null, url2, targetOrNum, lvmethod); break; case LOADMOVIE: ret = new LoadMovieActionItem(null, null, url2, targetOrNum, lvmethod); break; case LOADVARIABLESNUM: ret = new LoadVariablesNumActionItem(null, null, url2, targetOrNum, lvmethod); break; case LOADMOVIENUM: ret = new LoadMovieNumActionItem(null, null, url2, targetOrNum, lvmethod); break; } break; case GOTOANDPLAY: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem gtpFrame = expression(inFunction, inMethod, true, variables, functions); s = lex(); if (s.type == SymbolType.COMMA) { //Handle scene? lex(); gtpFrame = expression(inFunction, inMethod, true, variables, functions); } else { lexer.pushback(s); } ret = new GotoFrame2ActionItem(null, null, gtpFrame, false, true, 0); expectedType(SymbolType.PARENT_CLOSE); break; case REMOVEMOVIECLIP: expectedType(SymbolType.PARENT_OPEN); ret = new RemoveSpriteActionItem(null, null, (expression(inFunction, inMethod, true, variables, functions))); expectedType(SymbolType.PARENT_CLOSE); break; case STARTDRAG: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem dragTarget = (expression(inFunction, inMethod, true, variables, functions)); GraphTargetItem lockCenter; GraphTargetItem constrain; GraphTargetItem x1 = null; GraphTargetItem y1 = null; GraphTargetItem x2 = null; GraphTargetItem y2 = null; s = lex(); if (s.type == SymbolType.COMMA) { lockCenter = (expression(inFunction, inMethod, true, variables, functions)); s = lex(); if (s.type == SymbolType.COMMA) { constrain = new DirectValueActionItem(null, null, 0, 1L, new ArrayList<>()); x1 = (expression(inFunction, inMethod, true, variables, functions)); s = lex(); if (s.type == SymbolType.COMMA) { y1 = (expression(inFunction, inMethod, true, variables, functions)); s = lex(); if (s.type == SymbolType.COMMA) { x2 = (expression(inFunction, inMethod, true, variables, functions)); s = lex(); if (s.type == SymbolType.COMMA) { y2 = (expression(inFunction, inMethod, true, variables, functions)); } else { lexer.pushback(s); y2 = new DirectValueActionItem(null, null, 0, 0L, new ArrayList<>()); } } else { lexer.pushback(s); x2 = new DirectValueActionItem(null, null, 0, 0L, new ArrayList<>()); y2 = new DirectValueActionItem(null, null, 0, 0L, new ArrayList<>()); } } else { lexer.pushback(s); x2 = new DirectValueActionItem(null, null, 0, 0L, new ArrayList<>()); y2 = new DirectValueActionItem(null, null, 0, 0L, new ArrayList<>()); y1 = new DirectValueActionItem(null, null, 0, 0L, new ArrayList<>()); } } else { lexer.pushback(s); constrain = new DirectValueActionItem(null, null, 0, 0L, new ArrayList<>()); //ret.add(new ActionPush(Boolean.FALSE)); } } else { lockCenter = new DirectValueActionItem(null, null, 0, 0L, new ArrayList<>()); constrain = new DirectValueActionItem(null, null, 0, 0L, new ArrayList<>()); lexer.pushback(s); } expectedType(SymbolType.PARENT_CLOSE); ret = new StartDragActionItem(null, null, dragTarget, lockCenter, constrain, x1, y1, x2, y2); break; case IFFRAMELOADED: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem iflExpr = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); expectedType(SymbolType.CURLY_OPEN); List<GraphTargetItem> iflComs = commands(inFunction, inMethod, forinlevel, variables, functions); expectedType(SymbolType.CURLY_CLOSE); ret = new IfFrameLoadedActionItem(iflExpr, iflComs, null, null); break; case CLASS: GraphTargetItem classTypeStr = type(variables); s = lex(); GraphTargetItem extendsTypeStr = null; if (s.type == SymbolType.EXTENDS) { extendsTypeStr = type(variables); s = lex(); } List<GraphTargetItem> implementsTypeStrs = new ArrayList<>(); if (s.type == SymbolType.IMPLEMENTS) { do { GraphTargetItem implementsTypeStr = type(variables); implementsTypeStrs.add(implementsTypeStr); s = lex(); } while (s.type == SymbolType.COMMA); } expected(s, lexer.yyline(), SymbolType.CURLY_OPEN); ret = (traits(false, classTypeStr, extendsTypeStr, implementsTypeStrs, variables, functions)); expectedType(SymbolType.CURLY_CLOSE); break; case INTERFACE: GraphTargetItem interfaceTypeStr = type(variables); s = lex(); List<GraphTargetItem> intExtendsTypeStrs = new ArrayList<>(); if (s.type == SymbolType.EXTENDS) { do { GraphTargetItem intExtendsTypeStr = type(variables); intExtendsTypeStrs.add(intExtendsTypeStr); s = lex(); } while (s.type == SymbolType.COMMA); } expected(s, lexer.yyline(), SymbolType.CURLY_OPEN); ret = (traits(true, interfaceTypeStr, null, intExtendsTypeStrs, variables, functions)); expectedType(SymbolType.CURLY_CLOSE); break; case FUNCTION: s = lexer.lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER, SymbolGroup.GLOBALFUNC); ret = (function(true, s.value.toString(), false, variables, functions)); break; case VAR: s = lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER); String varIdentifier = s.value.toString(); s = lex(); if (s.type == SymbolType.COLON) { type(variables); s = lex(); //TODO: handle value type } if (s.type == SymbolType.ASSIGN) { GraphTargetItem varval = (expression(inFunction, inMethod, true, variables, functions)); ret = new VariableActionItem(varIdentifier, varval, true); variables.add((VariableActionItem) ret); } else { ret = new VariableActionItem(varIdentifier, null, true); variables.add((VariableActionItem) ret); lexer.pushback(s); } break; case CURLY_OPEN: ret = new BlockItem(null, null, commands(inFunction, inMethod, forinlevel, variables, functions)); expectedType(SymbolType.CURLY_CLOSE); break; case INCREMENT: //preincrement case DECREMENT: //predecrement GraphTargetItem varincdec = expression(inFunction, inMethod, false, variables, functions); if (s.type == SymbolType.INCREMENT) { ret = new PreIncrementActionItem(null, null, varincdec); } else if (s.type == SymbolType.DECREMENT) { ret = new PreDecrementActionItem(null, null, varincdec); } break; case SUPER: //constructor call ParsedSymbol ss2 = lex(); if (ss2.type == SymbolType.PARENT_OPEN) { List<GraphTargetItem> args = call(inFunction, inMethod, variables, functions); VariableActionItem supItem = new VariableActionItem(s.value.toString(), null, false); variables.add(supItem); ret = new CallMethodActionItem(null, null, supItem, new DirectValueActionItem(null, null, 0, Undefined.INSTANCE, constantPool), args); } else {//no costructor call, but it could be calling parent methods... => handle in expression lexer.pushback(ss2); lexer.pushback(s); } break; case IF: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem ifExpr = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); GraphTargetItem onTrue = command(inFunction, inMethod, forinlevel, true, variables, functions); List<GraphTargetItem> onTrueList = new ArrayList<>(); onTrueList.add(onTrue); s = lex(); List<GraphTargetItem> onFalseList = null; if (s.type == SymbolType.ELSE) { onFalseList = new ArrayList<>(); onFalseList.add(command(inFunction, inMethod, forinlevel, true, variables, functions)); } else { lexer.pushback(s); } ret = new IfItem(null, null, ifExpr, onTrueList, onFalseList); break; case WHILE: expectedType(SymbolType.PARENT_OPEN); List<GraphTargetItem> whileExpr = new ArrayList<>(); whileExpr.add(commaExpression(inFunction, inMethod, forinlevel, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); List<GraphTargetItem> whileBody = new ArrayList<>(); whileBody.add(command(inFunction, inMethod, forinlevel, true, variables, functions)); ret = new WhileItem(null, null, null, whileExpr, whileBody); break; case DO: List<GraphTargetItem> doBody = new ArrayList<>(); doBody.add(command(inFunction, inMethod, forinlevel, true, variables, functions)); expectedType(SymbolType.WHILE); expectedType(SymbolType.PARENT_OPEN); List<GraphTargetItem> doExpr = new ArrayList<>(); doExpr.add(commaExpression(inFunction, inMethod, forinlevel, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); ret = new DoWhileItem(null, null, null, doBody, doExpr); break; case FOR: expectedType(SymbolType.PARENT_OPEN); s = lex(); boolean forin = false; GraphTargetItem collection = null; String objIdent; VariableActionItem item = null; int innerExprReg = 0; boolean define = false; if (s.type == SymbolType.VAR || s.type == SymbolType.IDENTIFIER) { ParsedSymbol s2 = null; ParsedSymbol ssel = s; if (s.type == SymbolType.VAR) { s2 = lex(); ssel = s2; define = true; } if (ssel.type == SymbolType.IDENTIFIER) { objIdent = ssel.value.toString(); ParsedSymbol s3 = lex(); if (s3.type == SymbolType.IN) { if (inFunction) { /*for (int i = 0; i < 256; i++) { if (!registerVars.containsValue(i)) { registerVars.put(objIdent, i); innerExprReg = i; break; } }*/ } item = new VariableActionItem(objIdent, null, define); item.setStoreValue(new GraphTargetItem() { @Override public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException { return writer; } @Override public boolean hasReturnValue() { return false; } @Override public GraphTargetItem returnType() { return TypeItem.UNBOUNDED; } //toSource is Empty }); variables.add(item); collection = expression(inFunction, inMethod, true, variables, functions); forin = true; } else { lexer.pushback(s3); if (s2 != null) { lexer.pushback(s2); } lexer.pushback(s); } } else { if (s2 != null) { lexer.pushback(s2); } lexer.pushback(s); } } else { lexer.pushback(s); } List<GraphTargetItem> forFinalCommands = new ArrayList<>(); GraphTargetItem forExpr = null; List<GraphTargetItem> forFirstCommands = new ArrayList<>(); if (!forin) { GraphTargetItem fc = command(inFunction, inMethod, forinlevel, true, variables, functions); if (fc != null) { //can be empty command forFirstCommands.add(fc); } forExpr = (expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.SEMICOLON); GraphTargetItem fcom = command(inFunction, inMethod, forinlevel, true, variables, functions); if (fcom != null) { forFinalCommands.add(fcom); } } expectedType(SymbolType.PARENT_CLOSE); List<GraphTargetItem> forBody = new ArrayList<>(); forBody.add(command(inFunction, inMethod, forin ? forinlevel + 1 : forinlevel, true, variables, functions)); if (forin) { ret = new ForInActionItem(null, null, null, item, collection, forBody); } else { ret = new ForItem(null, null, null, forFirstCommands, forExpr, forFinalCommands, forBody); } break; case SWITCH: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem switchExpr = expression(inFunction, inMethod, true, variables, functions); expectedType(SymbolType.PARENT_CLOSE); expectedType(SymbolType.CURLY_OPEN); s = lex(); //ret.addAll(switchExpr); /*int exprReg = 0; for (int i = 0; i < 256; i++) { if (!registerVars.containsValue(i)) { registerVars.put("__switch" + uniqId(), i); exprReg = i; break; } }*/ List<List<ActionIf>> caseIfs = new ArrayList<>(); List<List<GraphTargetItem>> caseCmds = new ArrayList<>(); List<GraphTargetItem> caseExprsAll = new ArrayList<>(); List<Integer> valueMapping = new ArrayList<>(); int pos = 0; while (s.type == SymbolType.CASE || s.type == SymbolType.DEFAULT) { //List<GraphTargetItem> caseExprs; = new ArrayList<>(); while (s.type == SymbolType.CASE || s.type == SymbolType.DEFAULT) { GraphTargetItem curCaseExpr = s.type == SymbolType.DEFAULT ? new DefaultItem() : expression(inFunction, inMethod, true, variables, functions); //caseExprs.add(curCaseExpr); expectedType(SymbolType.COLON); s = lex(); caseExprsAll.add(curCaseExpr); valueMapping.add(pos); } pos++; lexer.pushback(s); List<GraphTargetItem> caseCmd = commands(inFunction, inMethod, forinlevel, variables, functions); caseCmds.add(caseCmd); s = lex(); } expected(s, lexer.yyline(), SymbolType.CURLY_CLOSE); ret = new SwitchItem(null, null, null, switchExpr, caseExprsAll, caseCmds, valueMapping); break; case BREAK: ret = new BreakItem(null, null, 0); //? There is no more than 1 level continue/break in AS1/2 break; case CONTINUE: ret = new ContinueItem(null, null, 0); //? There is no more than 1 level continue/break in AS1/2 break; case RETURN: GraphTargetItem retexpr = expression(inFunction, inMethod, true, variables, functions); if (retexpr == null) { retexpr = new DirectValueActionItem(null, null, 0, Undefined.INSTANCE, new ArrayList<>()); } ret = new ReturnActionItem(null, null, retexpr); break; case TRY: List<GraphTargetItem> tryCommands = new ArrayList<>(); tryCommands.add(command(inFunction, inMethod, forinlevel, true, variables, functions)); s = lex(); boolean found = false; List<List<GraphTargetItem>> catchCommands = null; List<GraphTargetItem> catchExceptions = new ArrayList<>(); if (s.type == SymbolType.CATCH) { expectedType(SymbolType.PARENT_OPEN); s = lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER, SymbolType.STRING); catchExceptions.add(pushConst((String) s.value)); expectedType(SymbolType.PARENT_CLOSE); catchCommands = new ArrayList<>(); List<GraphTargetItem> cc = new ArrayList<>(); cc.add(command(inFunction, inMethod, forinlevel, true, variables, functions)); catchCommands.add(cc); s = lex(); found = true; } List<GraphTargetItem> finallyCommands = null; if (s.type == SymbolType.FINALLY) { finallyCommands = new ArrayList<>(); finallyCommands.add(command(inFunction, inMethod, forinlevel, true, variables, functions)); found = true; s = lex(); } if (!found) { expected(s, lexer.yyline(), SymbolType.CATCH, SymbolType.FINALLY); } lexer.pushback(s); ret = new TryActionItem(tryCommands, catchExceptions, catchCommands, finallyCommands); break; case THROW: ret = new ThrowActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); break; case SEMICOLON: //empty command if (debugMode) { System.out.println("/command"); } return null; default: GraphTargetItem valcmd = expressionCommands(s, inFunction, inMethod, forinlevel, variables, functions); if (valcmd != null) { ret = valcmd; break; } lexer.pushback(s); ret = expression(inFunction, inMethod, true, variables, functions); } if (debugMode) { System.out.println("/command"); } lexer.removeListener(buf); if (ret == null) { //can be popped expression buf.pushAllBack(lexer); ret = expression(inFunction, inMethod, true, variables, functions); } s = lex(); if ((s != null) && (s.type != SymbolType.SEMICOLON)) { lexer.pushback(s); } return ret; } private GraphTargetItem expression(boolean inFunction, boolean inMethod, boolean allowRemainder, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { if (debugMode) { System.out.println("expression:"); } GraphTargetItem prim = expressionPrimary(false, inFunction, inMethod, allowRemainder, variables, functions); if (prim == null) { return null; } GraphTargetItem ret = expression1(prim, GraphTargetItem.NOPRECEDENCE, inFunction, inMethod, allowRemainder, variables, functions); if (debugMode) { System.out.println("/expression"); } return ret; } private ParsedSymbol peekLex() throws IOException, ActionParseException { ParsedSymbol lookahead = lex(); lexer.pushback(lookahead); return lookahead; } private static final String[] operatorIdentifiers = new String[]{"add", "eq", "ne", "lt", "ge", "gt", "le"}; private boolean isBinaryOperator(ParsedSymbol s) { if (s.type == SymbolType.IDENTIFIER && Arrays.asList(operatorIdentifiers).contains(s.value.toString())) { return true; } return s.type.isBinary(); } private int getSymbPrecedence(ParsedSymbol s) { if (s.type == SymbolType.IDENTIFIER && Arrays.asList(operatorIdentifiers).contains(s.value.toString())) { switch (s.value.toString()) { case "add": return GraphTargetItem.PRECEDENCE_ADDITIVE; case "eq": case "ne": return GraphTargetItem.PRECEDENCE_EQUALITY; case "lt": case "ge": case "gt": case "le": return GraphTargetItem.PRECEDENCE_RELATIONAL; } } return s.type.getPrecedence(); } private GraphTargetItem expression1(GraphTargetItem lhs, int min_precedence, boolean inFunction, boolean inMethod, boolean allowRemainder, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { ParsedSymbol op; GraphTargetItem rhs; GraphTargetItem mhs = null; ParsedSymbol lookahead = peekLex(); if (debugMode) { System.out.println("expression1:"); } //Note: algorithm from http://en.wikipedia.org/wiki/Operator-precedence_parser //with relation operators reversed as we have precedence in reverse order while (isBinaryOperator(lookahead) && getSymbPrecedence(lookahead) <= /* >= on wiki */ min_precedence) { op = lookahead; lex(); //Note: Handle ternar operator as Binary //http://stackoverflow.com/questions/13681293/how-can-i-incorporate-ternary-operators-into-a-precedence-climbing-algorithm if (op.type == SymbolType.TERNAR) { if (debugMode) { System.out.println("ternar-middle:"); } mhs = expression(inFunction, inMethod, allowRemainder, variables, functions); expectedType(SymbolType.COLON); if (debugMode) { System.out.println("/ternar-middle"); } } rhs = expressionPrimary(allowRemainder, inFunction, inMethod, allowRemainder, variables, functions); if (rhs == null) { lexer.pushback(op); break; } lookahead = peekLex(); while ((isBinaryOperator(lookahead) && getSymbPrecedence(lookahead) < /* > on wiki */ getSymbPrecedence(op)) || (lookahead.type.isRightAssociative() && getSymbPrecedence(lookahead) == getSymbPrecedence(op))) { rhs = expression1(rhs, getSymbPrecedence(lookahead), inFunction, inMethod, allowRemainder, variables, functions); lookahead = peekLex(); } switch (op.type) { case TERNAR: lhs = new TernarOpItem(null, null, lhs, mhs, rhs); break; case SHIFT_LEFT: lhs = new LShiftActionItem(null, null, lhs, rhs); break; case SHIFT_RIGHT: lhs = new RShiftActionItem(null, null, lhs, rhs); break; case USHIFT_RIGHT: lhs = new URShiftActionItem(null, null, lhs, rhs); break; case BITAND: lhs = new BitAndActionItem(null, null, lhs, rhs); break; case BITOR: lhs = new BitOrActionItem(null, null, lhs, rhs); break; case DIVIDE: lhs = new DivideActionItem(null, null, lhs, rhs); break; case MODULO: lhs = new ModuloActionItem(null, null, lhs, rhs); break; case EQUALS: lhs = new EqActionItem(null, null, lhs, rhs, true/*FIXME SWF version?*/); break; case STRICT_EQUALS: lhs = new StrictEqActionItem(null, null, lhs, rhs); break; case NOT_EQUAL: lhs = new NeqActionItem(null, null, lhs, rhs, true/*FIXME SWF version?*/); break; case STRICT_NOT_EQUAL: lhs = new StrictNeqActionItem(null, null, lhs, rhs); break; case LOWER_THAN: lhs = new LtActionItem(null, null, lhs, rhs, true/*FIXME SWF version?*/); break; case LOWER_EQUAL: lhs = new LeActionItem(null, null, lhs, rhs); break; case GREATER_THAN: lhs = new GtActionItem(null, null, lhs, rhs); break; case GREATER_EQUAL: lhs = new GeActionItem(null, null, lhs, rhs, true/*FIXME SWF version?*/); break; case AND: lhs = new AndItem(null, null, lhs, rhs); break; case OR: lhs = new OrItem(null, null, lhs, rhs); break; case FULLAND: lhs = new AndActionItem(null, null, lhs, rhs); break; case FULLOR: lhs = new OrActionItem(null, null, lhs, rhs); break; case MINUS: lhs = new SubtractActionItem(null, null, lhs, rhs); break; case MULTIPLY: lhs = new MultiplyActionItem(null, null, lhs, rhs); break; case PLUS: lhs = new AddActionItem(null, null, lhs, rhs, true); break; case XOR: lhs = new BitXorActionItem(null, null, lhs, rhs); break; case AS: break; case INSTANCEOF: lhs = new InstanceOfActionItem(null, null, lhs, rhs); break; case IS: break; case ASSIGN: case ASSIGN_BITAND: case ASSIGN_BITOR: case ASSIGN_DIVIDE: case ASSIGN_MINUS: case ASSIGN_MODULO: case ASSIGN_MULTIPLY: case ASSIGN_PLUS: case ASSIGN_SHIFT_LEFT: case ASSIGN_SHIFT_RIGHT: case ASSIGN_USHIFT_RIGHT: case ASSIGN_XOR: GraphTargetItem assigned = rhs; switch (lookahead.type) { case ASSIGN: //assigned = assigned; break; case ASSIGN_BITAND: assigned = new BitAndActionItem(null, null, lhs, assigned); break; case ASSIGN_BITOR: assigned = new BitOrActionItem(null, null, lhs, assigned); break; case ASSIGN_DIVIDE: assigned = new DivideActionItem(null, null, lhs, assigned); break; case ASSIGN_MINUS: assigned = new SubtractActionItem(null, null, lhs, assigned); break; case ASSIGN_MODULO: assigned = new ModuloActionItem(null, null, lhs, assigned); break; case ASSIGN_MULTIPLY: assigned = new MultiplyActionItem(null, null, lhs, assigned); break; case ASSIGN_PLUS: assigned = new AddActionItem(null, null, lhs, assigned, true/*TODO:SWF version?*/); break; case ASSIGN_SHIFT_LEFT: assigned = new LShiftActionItem(null, null, lhs, assigned); break; case ASSIGN_SHIFT_RIGHT: assigned = new RShiftActionItem(null, null, lhs, assigned); break; case ASSIGN_USHIFT_RIGHT: assigned = new URShiftActionItem(null, null, lhs, assigned); break; case ASSIGN_XOR: assigned = new BitXorActionItem(null, null, lhs, assigned); break; } if (lhs instanceof VariableActionItem) { ((VariableActionItem) lhs).setStoreValue(assigned); ((VariableActionItem) lhs).setDefinition(false); lhs = lhs; } else if (lhs instanceof GetMemberActionItem) { lhs = new SetMemberActionItem(null, null, ((GetMemberActionItem) lhs).object, ((GetMemberActionItem) lhs).memberName, assigned); } else { throw new ActionParseException("Invalid assignment", lexer.yyline()); } break; case IDENTIFIER: switch (lookahead.value.toString()) { case "add": lhs = new StringAddActionItem(null, null, lhs, rhs); break; case "eq": lhs = new StringEqActionItem(null, null, lhs, rhs); break; case "ne": lhs = new StringNeActionItem(null, null, lhs, rhs); break; case "lt": lhs = new StringLtActionItem(null, null, lhs, rhs); break; case "ge": lhs = new StringGeActionItem(null, null, lhs, rhs); break; case "gt": lhs = new StringGtActionItem(null, null, lhs, rhs); break; case "le": lhs = new StringLeActionItem(null, null, lhs, rhs); break; } break; } } switch (lookahead.type) { case INCREMENT: //postincrement lex(); if (!(lhs instanceof VariableActionItem) && !(lhs instanceof GetMemberActionItem)) { throw new ActionParseException("Invalid assignment", lexer.yyline()); } lhs = new PostIncrementActionItem(null, null, lhs); break; case DECREMENT: //postdecrement lex(); if (!(lhs instanceof VariableActionItem) && !(lhs instanceof GetMemberActionItem)) { throw new ActionParseException("Invalid assignment", lexer.yyline()); } lhs = new PostDecrementActionItem(null, null, lhs); break; default: if (lhs instanceof ParenthesisItem) { if (isType(((ParenthesisItem) lhs).value)) { GraphTargetItem expr2 = expression(inFunction, inMethod, true, variables, functions); if (expr2 != null) { lhs = new CastOpActionItem(null, null, ((ParenthesisItem) lhs).value, expr2); } } } } if (debugMode) { System.out.println("/expression1"); } return lhs; } private boolean isType(GraphTargetItem item) { if (item == null) { return false; } while (item instanceof GetMemberActionItem) { item = ((GetMemberActionItem) item).object; } return (item instanceof VariableActionItem); } private int brackets(List<GraphTargetItem> ret, boolean inFunction, boolean inMethod, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { ParsedSymbol s = lex(); int arrCnt = 0; if (s.type == SymbolType.BRACKET_OPEN) { s = lex(); while (s.type != SymbolType.BRACKET_CLOSE) { if (s.type != SymbolType.COMMA) { lexer.pushback(s); } arrCnt++; ret.add(expression(inFunction, inMethod, true, variables, functions)); s = lex(); if (!s.isType(SymbolType.COMMA, SymbolType.BRACKET_CLOSE)) { expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.BRACKET_CLOSE); } } } else { lexer.pushback(s); return -1; } return arrCnt; } private GraphTargetItem commaExpression(boolean inFunction, boolean inMethod, int forInLevel, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { GraphTargetItem cmd = null; List<GraphTargetItem> expr = new ArrayList<>(); ParsedSymbol s; do { cmd = command(inFunction, inMethod, forInLevel, false, variables, functions); if (cmd != null) { expr.add(cmd); } s = lex(); } while (s.type == SymbolType.COMMA && cmd != null); lexer.pushback(s); if (cmd == null) { expr.add(expression(inFunction, inMethod, true, variables, functions)); } else if (!cmd.hasReturnValue()) { throw new ActionParseException("Expression expected", lexer.yyline()); } return new CommaExpressionItem(null, null, expr); } private GraphTargetItem expressionPrimary(boolean allowEmpty, boolean inFunction, boolean inMethod, boolean allowRemainder, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { if (debugMode) { System.out.println("primary:"); } boolean allowMemberOrCall = false; GraphTargetItem ret = null; ParsedSymbol s = lex(); switch (s.type) { case PREPROCESSOR: expectedType(SymbolType.PARENT_OPEN); switch ("" + s.value) { //AS 1/2: case "enumerate": ret = new EnumerateActionItem(null, null, expression(inFunction, inMethod, allowRemainder, variables, functions)); break; //Both ASs case "dup": ret = new DuplicateItem(null, null, expression(inFunction, inMethod, allowRemainder, variables, functions)); break; case "push": ret = new PushItem(expression(inFunction, inMethod, allowRemainder, variables, functions)); break; case "pop": ret = new PopItem(null, null); break; case "goto": //TODO throw new ActionParseException("Compiling §§" + s.value + " is not available, sorry", lexer.yyline()); default: throw new ActionParseException("Unknown preprocessor instruction: §§" + s.value, lexer.yyline()); } expectedType(SymbolType.PARENT_CLOSE); break; case NEGATE: versionRequired(s, 5); ret = expressionPrimary(false, inFunction, inMethod, false, variables, functions); ret = new BitXorActionItem(null, null, ret, new DirectValueActionItem(4.294967295E9)); break; case MINUS: s = lex(); if (s.isType(SymbolType.DOUBLE)) { ret = new DirectValueActionItem(null, null, 0, -(double) (Double) s.value, new ArrayList<>()); } else if (s.isType(SymbolType.INTEGER)) { ret = new DirectValueActionItem(null, null, 0, -(long) (Long) s.value, new ArrayList<>()); } else { lexer.pushback(s); GraphTargetItem num = expressionPrimary(false, inFunction, inMethod, true, variables, functions); if ((num instanceof DirectValueActionItem) && (((DirectValueActionItem) num).value instanceof Long)) { ((DirectValueActionItem) num).value = -(Long) ((DirectValueActionItem) num).value; ret = num; } else if ((num instanceof DirectValueActionItem) && (((DirectValueActionItem) num).value instanceof Double)) { Double d = (Double) ((DirectValueActionItem) num).value; if (d.isInfinite()) { ((DirectValueActionItem) num).value = Double.NEGATIVE_INFINITY; } else { ((DirectValueActionItem) num).value = -d; } ret = (num); } else if ((num instanceof DirectValueActionItem) && (((DirectValueActionItem) num).value instanceof Float)) { ((DirectValueActionItem) num).value = -(Float) ((DirectValueActionItem) num).value; ret = (num); } else {; ret = (new SubtractActionItem(null, null, new DirectValueActionItem(null, null, 0, (Long) 0L, new ArrayList<>()), num)); } } break; case TYPEOF: ret = new TypeOfActionItem(null, null, expressionPrimary(false, inFunction, inMethod, false, variables, functions)); allowMemberOrCall = true; break; case TRUE: ret = new DirectValueActionItem(null, null, 0, Boolean.TRUE, new ArrayList<>()); break; case NULL: ret = new DirectValueActionItem(null, null, 0, Null.INSTANCE, new ArrayList<>()); break; case UNDEFINED: ret = new DirectValueActionItem(null, null, 0, Undefined.INSTANCE, new ArrayList<>()); break; case FALSE: ret = new DirectValueActionItem(null, null, 0, Boolean.FALSE, new ArrayList<>()); break; case CURLY_OPEN: //Object literal s = lex(); List<GraphTargetItem> objectNames = new ArrayList<>(); List<GraphTargetItem> objectValues = new ArrayList<>(); while (s.type != SymbolType.CURLY_CLOSE) { if (s.type != SymbolType.COMMA) { lexer.pushback(s); } s = lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER); objectNames.add(0, pushConst((String) s.value)); expectedType(SymbolType.COLON); objectValues.add(0, expression(inFunction, inMethod, true, variables, functions)); s = lex(); if (!s.isType(SymbolType.COMMA, SymbolType.CURLY_CLOSE)) { expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.CURLY_CLOSE); } } ret = new InitObjectActionItem(null, null, objectNames, objectValues); allowMemberOrCall = true; break; case BRACKET_OPEN: //Array literal or just brackets lexer.pushback(s); List<GraphTargetItem> inBrackets = new ArrayList<>(); int arrCnt = brackets(inBrackets, inFunction, inMethod, variables, functions); ret = new InitArrayActionItem(null, null, inBrackets); allowMemberOrCall = true; break; case FUNCTION: s = lex(); String fname = ""; if (s.isType(SymbolType.IDENTIFIER, SymbolGroup.GLOBALFUNC)) { fname = s.value.toString(); } else { lexer.pushback(s); } ret = function(true, fname, false, variables, functions); allowMemberOrCall = true; break; case STRING: ret = pushConst(s.value.toString()); allowMemberOrCall = true; break; case NEWLINE: ret = new DirectValueActionItem(null, null, 0, "\r", new ArrayList<>()); allowMemberOrCall = true; break; case NAN: ret = new DirectValueActionItem(null, null, 0, Double.NaN, new ArrayList<>()); break; case INFINITY: ret = new DirectValueActionItem(null, null, 0, Double.POSITIVE_INFINITY, new ArrayList<>()); break; case INTEGER: case DOUBLE: ret = new DirectValueActionItem(null, null, 0, s.value, new ArrayList<>()); break; case DELETE: GraphTargetItem varDel = expressionPrimary(false, inFunction, inMethod, false, variables, functions); if (varDel instanceof GetMemberActionItem) { GetMemberActionItem gm = (GetMemberActionItem) varDel; ret = new DeleteActionItem(null, null, gm.object, gm.memberName); } else { throw new ActionParseException("Not a property", lexer.yyline()); } break; case INCREMENT: case DECREMENT: //preincrement GraphTargetItem prevar = expressionPrimary(false, inFunction, inMethod, false, variables, functions);//variable(inFunction, inMethod, variables, functions); if (s.type == SymbolType.INCREMENT) { ret = new PreIncrementActionItem(null, null, prevar); } if (s.type == SymbolType.DECREMENT) { ret = new PreDecrementActionItem(null, null, prevar); } break; case NOT: ret = new NotItem(null, null, expressionPrimary(false, inFunction, inMethod, false, variables, functions)); break; case PARENT_OPEN: ret = new ParenthesisItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); //ret = memberOrCall(ret, inFunction, inMethod, variables, functions); allowMemberOrCall = true; break; case NEW: GraphTargetItem newvar = expressionPrimary(false, inFunction, inMethod, false, variables, functions);//variable(inFunction, inMethod, variables, functions); if (newvar instanceof CallMethodActionItem) { CallMethodActionItem ca = (CallMethodActionItem) newvar; ret = new NewMethodActionItem(null, null, ca.scriptObject, ca.methodName, ca.arguments); } else if (newvar instanceof CallFunctionActionItem) { CallFunctionActionItem cf = (CallFunctionActionItem) newvar; ret = new NewObjectActionItem(null, null, cf.functionName, cf.arguments); } else { throw new ActionParseException("Invalid new item", lexer.yyline()); } break; case EVAL: expectedType(SymbolType.PARENT_OPEN); GraphTargetItem evar = new EvalActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); expectedType(SymbolType.PARENT_CLOSE); //evar = memberOrCall(evar, inFunction, inMethod, variables, functions); ret = evar; allowMemberOrCall = true; break; case IDENTIFIER: case THIS: case SUPER: if (s.value.equals("not")) { ret = new NotItem(null, null, expressionPrimary(false, inFunction, inMethod, false, variables, functions)); } else { ret = new VariableActionItem(s.value.toString(), null, false); variables.add((VariableActionItem) ret); allowMemberOrCall = true; } break; default: GraphTargetItem excmd = expressionCommands(s, inFunction, inMethod, -1, variables, functions); if (excmd != null) { //? ret = excmd; allowMemberOrCall = true; //? break; } lexer.pushback(s); } if (allowMemberOrCall && ret != null) { ret = memberOrCall(ret, inFunction, inMethod, variables, functions); } if (debugMode) { System.out.println("/primary"); } return ret; } private GraphTargetItem memberOrCall(GraphTargetItem ret, boolean inFunction, boolean inMethod, List<VariableActionItem> variables, List<FunctionActionItem> functions) throws IOException, ActionParseException { ParsedSymbol op = lex(); while (op.isType(SymbolType.PARENT_OPEN, SymbolType.BRACKET_OPEN, SymbolType.DOT)) { if (op.type == SymbolType.PARENT_OPEN) { List<GraphTargetItem> args = call(inFunction, inMethod, variables, functions); if (ret instanceof GetMemberActionItem) { GetMemberActionItem mem = (GetMemberActionItem) ret; ret = new CallMethodActionItem(null, null, mem.object, mem.memberName, args); } else if (ret instanceof VariableActionItem) { VariableActionItem var = (VariableActionItem) ret; ret = new CallFunctionActionItem(null, null, pushConst(var.getVariableName()), args); } else { ret = new CallFunctionActionItem(null, null, ret, args); } } if (op.type == SymbolType.BRACKET_OPEN) { GraphTargetItem rhs = expression(inFunction, inMethod, false, variables, functions); ret = new GetMemberActionItem(null, null, ret, rhs); expectedType(SymbolType.BRACKET_CLOSE); } if (op.type == SymbolType.DOT) { ParsedSymbol s = lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER, SymbolType.THIS, SymbolType.SUPER, SymbolType.STRING_OP, SymbolGroup.GLOBALFUNC); ret = new GetMemberActionItem(null, null, ret, pushConst(s.value.toString())); } op = lex(); } lexer.pushback(op); return ret; } private DirectValueActionItem pushConst(String s) throws IOException, ActionParseException { int index = constantPool.indexOf(s); if (index == -1) { if (ActionConstantPool.calculateSize(constantPool) + ActionConstantPool.calculateSize(s) <= 0xffff) { // constant pool is not full constantPool.add(s); index = constantPool.indexOf(s); } } if (index == -1) { return new DirectValueActionItem(null, null, 0, s, constantPool); } return new DirectValueActionItem(null, null, 0, new ConstantIndex(index), constantPool); } private ActionScriptLexer lexer = null; private List<String> constantPool; public List<GraphTargetItem> treeFromString(String str, List<String> constantPool) throws ActionParseException, IOException { List<GraphTargetItem> retTree = new ArrayList<>(); this.constantPool = constantPool; lexer = new ActionScriptLexer(new StringReader(str)); List<VariableActionItem> vars = new ArrayList<>(); List<FunctionActionItem> functions = new ArrayList<>(); retTree.addAll(commands(false, false, 0, vars, functions)); for (VariableActionItem v : vars) { String varName = v.getVariableName(); GraphTargetItem stored = v.getStoreValue(); if (v.isDefinition()) { v.setBoxedValue(new DefineLocalActionItem(null, null, pushConst(varName), stored)); } else if (stored != null) { v.setBoxedValue(new SetVariableActionItem(null, null, pushConst(varName), stored)); } else { v.setBoxedValue(new GetVariableActionItem(null, null, pushConst(varName))); } } if (lexer.lex().type != SymbolType.EOF) { throw new ActionParseException("Parsing finished before end of the file", lexer.yyline()); } return retTree; } public List<Action> actionsFromTree(List<GraphTargetItem> tree, List<String> constantPool) throws CompilationException { ActionSourceGenerator gen = new ActionSourceGenerator(swfVersion, constantPool); List<Action> ret = new ArrayList<>(); SourceGeneratorLocalData localData = new SourceGeneratorLocalData( new HashMap<>(), 0, Boolean.FALSE, 0); List<GraphSourceItem> srcList = gen.generate(localData, tree); for (GraphSourceItem s : srcList) { if (s instanceof Action) { ret.add((Action) s); } } ret.add(0, new ActionConstantPool(constantPool)); return ret; } public List<Action> actionsFromString(String s) throws ActionParseException, IOException, CompilationException { List<String> constantPool = new ArrayList<>(); List<GraphTargetItem> tree = treeFromString(s, constantPool); return actionsFromTree(tree, constantPool); } private void versionRequired(ParsedSymbol s, int min) throws ActionParseException { versionRequired(s.value.toString(), min, Integer.MAX_VALUE); } private void versionRequired(ParsedSymbol s, int min, int max) throws ActionParseException { versionRequired(s.value.toString(), min, max); } private void versionRequired(String type, int min, int max) throws ActionParseException { if (min == max && swfVersion != min) { throw new ActionParseException(type + " requires SWF version " + min, lexer.yyline()); } if (swfVersion < min) { throw new ActionParseException(type + " requires at least SWF version " + min, lexer.yyline()); } if (swfVersion > max) { throw new ActionParseException(type + " requires SWF version lower than " + max, lexer.yyline()); } } }