protected Expression ToExpression(DataElement element) { switch (element) { case DataElement.Ctrl ctrl: return(new JumpLabelExpr { LabelStmt = new JumpLabelStmt { Name = ctrl.Name.StringValue.EscapeIdentifier() } }); case DataElement.CInt cint when cint.Value.IsPointer: return(new PointerLiteral { PointerId = cint.Value.PointerId }); case DataElement.CInt cint: return(new IntegerLiteral { Value = cint.Value.IntValue }); case DataElement.CStr cstr: return(new StringLiteral { Value = cstr.Value.StringValue }); case DataElement.SStr sstr: return(new Variable { VariableType = sstr.FlagType.StringValue }); case DataElement.VInt vint when vint.FlagId.IsPointer: return(new VariablePointer { VariableType = vint.FlagType.StringValue, PointerId = vint.FlagId.PointerId }); case DataElement.VInt vint: return(new Variable { VariableType = vint.FlagType.StringValue, VariableId = vint.FlagId.IntValue }); case DataElement.VStr vstr when vstr.FlagId.IsPointer: return(new VariablePointer { VariableType = vstr.FlagType.StringValue, PointerId = vstr.FlagId.PointerId }); case DataElement.VStr vstr: return(new Variable { VariableType = vstr.FlagType.StringValue, VariableId = vstr.FlagId.IntValue }); case DataElement.VLoc vloc: if (!_locals.ContainsKey(vloc.Id)) { throw new FormatException("Use of undefined local variable"); } var local = _locals[vloc.Id]; if (local == null) { throw new FormatException("Repeated use of the same local variable"); } _locals[vloc.Id] = null; return(local); default: throw new FormatException("Invalid expression element: " + element.Type); } }
public AssignmentScriptElement(DataElement var, ScriptElement expression) { this.var = var; this.expression = expression; }
public DataScriptElement(DataElement elem) { this.elem = elem; }
/** * Reads the next script element from the stream. * This could be an entire command or a single parameter. */ ScriptElement NextScriptElement(Stream s) { BinaryReader br = new BinaryReader(s); ScriptElement elem = null; // read the next data element. DataElement cmd = NextDataElement(s); // if no data element could be found, ... if (cmd == null) { // ...return nothing. return(null); } // if the element is... switch (cmd.type) { // ...a function call, then... case DataElement.TYPE_FUNC: { // ...determine how many parameters the function call takes. int paramCount = br.ReadInt32(); ScriptElement[] parameters = new ScriptElement[paramCount]; // for each parameter... for (var j = 0; j < paramCount; j++) { // ...read it's value DataElement param = NextDataElement(s); // if the parameter is a temporary variable reference, then... if (param is VarTempRefDataElement) { // ...replace it by the appropriate substitution expression. parameters[j] = substituteExpressions[((VarTempRefDataElement)param).id]; // after that, continue with the next parameter. continue; } // if the function is called... // ..."StrOut" and this parameter is a string, ... if ((((FuncDataElement)cmd).name.Equals("StrOut") || ((FuncDataElement)cmd).name.Equals("StrOutNW")) && param is StringDataElement) { // (...then this is a line of text to be displayed.) // (we want to store those in a separate file to make it easier to translate them.) // get the text string line = ((StringDataElement)param).data; // generate a new line number string id = "L" + (++lineCount); // add those to the list of known strings... stringTable.Add(id, new ScriptLine(fontID, speaker, line)); // ...and replace the string parameter with reference to the line number parameters[j] = new DataScriptElement(new ExternalStringDataElement(id, stringTable)); } // ..."StrOutNWC", ... else if (((FuncDataElement)cmd).name.Equals("StrOutNWC")) { // (...then this parameter represents a name.) // if the parameter is... // ...a string, then... if (param is StringDataElement) { // (...we want to store it separately to make it easier to translate.) string name = ((StringDataElement)param).data; // set the current speaker to this name. speaker = name; string id = null; // for each entry on our list of known strings... foreach (var entry in stringTable) { // ...check if it is a name. if (entry.Key[0] == 'N' && entry.Value.Text.Equals(name)) { // if it is, remember it's ID... id = entry.Key; // ...and stop searching. break; } } // if you didn't find a matching ID, ... if (id == null) { // generate a new one... id = "N" + (++nameCount); // ...and add it - together with the name - to the list of known strings. stringTable.Add(id, new ScriptLine(-1, null, name)); } // finally, replace the parameter by a reference to the name ID. parameters[j] = new DataScriptElement(new ExternalStringDataElement(id, stringTable)); } // ...a variable string reference, then... else if (param is VarStringRefDataElement) { // ...set the current speaker to "me"... speaker = "me"; // ...and keep the parameter as it is. parameters[j] = new DataScriptElement(param); } // ...something else, ... else { // ...then we just keep it as it is. parameters[j] = new DataScriptElement(param); } } // ...none of the above, ... else { // ...then we just keep the parameter as it is. parameters[j] = new DataScriptElement(param); } } // if the function is called "PF" (PrintFlush?), then... if (((FuncDataElement)cmd).name.Equals("PF")) { // ...reset the current speaker. speaker = ""; } // if the function is called "FontSet" and the second parameter is an integer, then... if (((FuncDataElement)cmd).name.Equals("FontSet") && parameters.Length >= 2 && parameters[1] is DataScriptElement dataElem && dataElem.elem is IntDataElement intElem) { // ...keep track of the current font fontID = intElem.data; } // save the current position in the code stream. long pos = s.Position; // read the next data element DataElement next = NextDataElement(s); // check if the next data element is a opening control flow element. // if it is, then... if (next is ControlDataElement && ((ControlDataElement)next).name.Equals("{")) { // ...create an empty list of script elements. List <ScriptElement> body = new List <ScriptElement>(); ScriptElement cur; // (read script elements and add them to the list of commands until you encounter a closing flow control element.) // repeat: while (true) { // read the next script element. cur = NextScriptElement(s); // if it is a closing flow control element, ... if (cur is JumpLabelScriptElement && ((JumpLabelScriptElement)cur).name.Equals("}")) { // ...stop repeating immediately break; } // otherwise, add it to the list of commands. body.Add(cur); } // if the function is a branching operation, then... if (((FuncDataElement)cmd).name.Equals("if")) { // ...save the current position in the code stream. pos = s.Position; // read the next data element next = NextDataElement(s); // check if the next data element is a else clause. // if it is, then... if (next is ControlDataElement && ((ControlDataElement)next).name.Equals("else")) { // ...discard the opening flow control element. NextDataElement(s); // ...create an empty list of script elements. List <ScriptElement> falsebody = new List <ScriptElement>(); // (read script elements and add them to the list of commands until you encounter a closing flow control element.) // repeat: while (true) { // read the next script element. cur = NextScriptElement(s); // if it is a closing flow control element, ... if (cur is JumpLabelScriptElement && ((JumpLabelScriptElement)cur).name.Equals("}")) { // ...stop repeating immediately break; } // otherwise, add it to the list of commands. falsebody.Add(cur); } // use the first (and only) parameter of the function call as branching condition, wrap it - together with the code blocks - in a script element and save it as the result. elem = new BranchScriptElement(parameters[0], body, falsebody); } // it it's not, then... else { // ...jump back to the previously saved code position. s.Seek(pos, SeekOrigin.Begin); // use the first (and only) parameter of the function call as branching condition, wrap it - together with the code block - in a script element and save it as the result. elem = new BranchScriptElement(parameters[0], body, null); } } // if it it not, then else { elem = new SwitchFunctionScriptElement(((FuncDataElement)cmd).name, parameters, body); } } // if not, ... else { // ...jump back to the previously saved code position. s.Seek(pos, SeekOrigin.Begin); // wrap the method name and and list of parameters in a script element and save it as the result. elem = new FuncCallScriptElement(((FuncDataElement)cmd).name, parameters); } break; } // ...a flow control element, then... case DataElement.TYPE_CTRL: { // ...create a new jump label with the same name and save it as the result. elem = new JumpLabelScriptElement(((ControlDataElement)cmd).name); break; } // ...a variable int or string reference, then... case DataElement.TYPE_VINT: case DataElement.TYPE_VSTR: { // (...we expect an assignment operation to follow.) // read assigned expression. ScriptElement expression = NextAssignment(s); // wrap the variable int/string reference and the expression in a new script element and save it as the result. elem = new AssignmentScriptElement(cmd, expression); break; } // ...a temporary variable id, then... case DataElement.TYPE_VTMP: { // (...we expect an assignment operation to follow.) // (after that we expect a command which uses this temporary variable.) // read assigned expression. ScriptElement expression = NextAssignment(s); // set the current temp variable expression to the one we just read... substituteExpressions[((VarTempRefDataElement)cmd).id] = expression; // ...and return nothing this time. break; } // ...an integer constant, then... case DataElement.TYPE_CINT: { // (...we have reached a very confusing part of this binary script format.) // (we expect an assignment to follow, basically "0 = <something>".) // (this sets the current flag pointer (represented by the 0) to <something>.) // (the flag pointed to by the pointer can be a accessed by using flag_$, globalflag_$, str_$ or globalstr_$.) // read the next script element (the expected assignment). FuncCallScriptElement assignment = NextScriptElement(s) as FuncCallScriptElement; ScriptElement expression = null; // if the assignment has... // ...more than 1 element, ... if (assignment.parameters.Length > 1) { // ...then create an expression from those. expression = new ExpressionScriptElement(assignment.parameters); } // ...exactly 1 element, ... else if (assignment.parameters.Length == 1) { // ...then it already is a valid expression. expression = assignment.parameters[0]; } // ...no elements, ... else { // ...then read the next script element and use it as expression. expression = NextScriptElement(s); } // wrap the integer and the expression in a new script element and save it as the result. elem = new AssignmentScriptElement(cmd, expression); break; } // ...something else, ... default: { // the script is probably damaged, so we throw an error. throw new Exception("Script format invalid"); } } // return the saved result return(elem); }
/// <summary> /// Flattens nested script elements to a list /// </summary> /// <param name="elem">The element to flatten</param> /// <param name="nested">Whether this is NOT a top-level element</param> /// <returns>The flattened list of elements</returns> List <ScriptElement> Flatten(ScriptElement elem, bool nested) { List <ScriptElement> list = new List <ScriptElement>(); if (elem is FuncCallScriptElement) { // new parameter array ScriptElement[] parameters = new ScriptElement[(elem as FuncCallScriptElement).parameters.Length]; for (int i = 0; i < parameters.Length; i++) { ScriptElement param = (elem as FuncCallScriptElement).parameters[i]; if (param is DataScriptElement) { parameters[i] = param; } else { // flatten all non-data parameters list.AddRange(Flatten(param, true)); parameters[i] = new DataScriptElement(new VarTempRefDataElement(tempVarID - 1)); } } if (nested) { list.Add(new DataScriptElement(new VarTempRefDataElement(tempVarID++))); list.Add(new FuncCallScriptElement("=", new ScriptElement[0])); } list.Add(new FuncCallScriptElement((elem as FuncCallScriptElement).name, parameters)); } else if (elem is ExpressionScriptElement) { ScriptElement[] parameters = new ScriptElement[(elem as ExpressionScriptElement).parameters.Length + (elem as ExpressionScriptElement).operators.Length]; for (int i = 0; i < (elem as ExpressionScriptElement).parameters.Length; i++) { if (i > 0) { string op = (elem as ExpressionScriptElement).operators[i - 1]; if (op.Equals("==")) { op = "="; } parameters[i * 2 - 1] = new DataScriptElement(new ControlDataElement(op)); } ScriptElement param = (elem as ExpressionScriptElement).parameters[i]; if (param is DataScriptElement) { parameters[i * 2] = param; } else { // flatten all non-data parameters list.AddRange(Flatten(param, true)); parameters[i * 2] = new DataScriptElement(new VarTempRefDataElement(tempVarID - 1)); } } list.Add(new DataScriptElement(new VarTempRefDataElement(tempVarID++))); list.Add(new FuncCallScriptElement("=", parameters)); } else if (elem is BranchScriptElement) { ScriptElement cond = (elem as BranchScriptElement).condition; if (!(cond is DataScriptElement)) { list.AddRange(Flatten(cond, true)); cond = new DataScriptElement(new VarTempRefDataElement(tempVarID - 1)); } ControlDataElement openingThen = new ControlDataElement("{"), closingThen = new ControlDataElement("}"), openingElse = null, closingElse = null; if ((elem as BranchScriptElement).falsebody != null) { openingElse = new ControlDataElement("{"); closingElse = new ControlDataElement("}"); openingThen.link = openingElse; closingThen.link = openingThen; openingElse.link = closingElse; closingElse.link = openingElse; } else { openingThen.link = closingThen; closingThen.link = openingThen; } list.Add(new FuncCallScriptElement("if", new ScriptElement[] { cond })); list.Add(new DataScriptElement(openingThen)); foreach (ScriptElement cmd in (elem as BranchScriptElement).truebody) { list.AddRange(Flatten(cmd, false)); } list.Add(new DataScriptElement(closingThen)); if ((elem as BranchScriptElement).falsebody != null) { list.Add(new DataScriptElement(new ControlDataElement("else"))); list.Add(new DataScriptElement(openingElse)); foreach (ScriptElement cmd in (elem as BranchScriptElement).falsebody) { list.AddRange(Flatten(cmd, false)); } list.Add(new DataScriptElement(closingElse)); } } else if (elem is SwitchFunctionScriptElement) { // new parameter array ScriptElement[] parameters = new ScriptElement[(elem as SwitchFunctionScriptElement).parameters.Length]; for (int i = 0; i < parameters.Length; i++) { ScriptElement param = (elem as SwitchFunctionScriptElement).parameters[i]; if (param is DataScriptElement) { parameters[i] = param; } else { // flatten all non-data parameters list.AddRange(Flatten(param, true)); parameters[i] = new DataScriptElement(new VarTempRefDataElement(tempVarID - 1)); } } list.Add(new FuncCallScriptElement((elem as SwitchFunctionScriptElement).name, parameters)); ControlDataElement opening = new ControlDataElement("{"); ControlDataElement closing = new ControlDataElement("}"); opening.link = closing; closing.link = opening; list.Add(new DataScriptElement(opening)); foreach (ScriptElement cmd in (elem as SwitchFunctionScriptElement).body) { list.AddRange(Flatten(cmd, false)); } list.Add(new DataScriptElement(closing)); } else if (elem is AssignmentScriptElement) { ScriptElement expr = (elem as AssignmentScriptElement).expression; DataElement var = (elem as AssignmentScriptElement).var; if (expr is DataScriptElement) { // special exception for the $ variable (e.g. clear_main.yks) if (var is IntDataElement) { list.Add(new DataScriptElement(new VarTempRefDataElement(tempVarID++))); list.Add(new FuncCallScriptElement("=", new[] { expr })); list.Add(new DataScriptElement(var)); list.Add(new FuncCallScriptElement("=", new[] { new DataScriptElement(new VarTempRefDataElement(tempVarID - 1)) })); } else { list.Add(new DataScriptElement(var)); list.Add(new FuncCallScriptElement("=", new[] { expr })); } } else { List <ScriptElement> cmds = Flatten(expr, true); // special exception for the $ variable (e.g. clear_main.yks) if (var is IntDataElement) { list.AddRange(cmds); list.Add(new DataScriptElement(var)); list.Add(new FuncCallScriptElement("=", new[] { new DataScriptElement(new VarTempRefDataElement(tempVarID - 1)) })); } else { // replace the last temp var reference by the variable for (int i = cmds.Count - 1; i >= 0; i--) { if (cmds[i] is DataScriptElement && (cmds[i] as DataScriptElement).elem is VarTempRefDataElement) { cmds[i] = new DataScriptElement((elem as AssignmentScriptElement).var); list.AddRange(cmds); return(list); } } throw new Exception("No VarTempRefDataElement found"); } } } else if (elem is JumpLabelScriptElement) { list.Add(new DataScriptElement(new ControlDataElement((elem as JumpLabelScriptElement).name))); } return(list); }
/// <summary> /// Compiles an entire script into a binary script file (.yks) /// </summary> /// <param name="script">The script instance to compile</param> /// <param name="s">The output stream</param> /// <returns>The number of bytes written</returns> public long ToBinary(YukaScript script, Stream s) { long offset = s.Position; List <ScriptElement> flattened = new List <ScriptElement>(); Console.ForegroundColor = ConsoleColor.Cyan; foreach (ScriptElement elem in script.commands) { List <ScriptElement> cmds = Flatten(elem, false); flattened.AddRange(cmds); if (cmds.Count == 0 && FlagCollection.current.Has('v')) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("?????????????"); Console.ForegroundColor = ConsoleColor.Cyan; } foreach (ScriptElement cmd in cmds) { if (cmd is DataScriptElement dse && dse.elem is ControlDataElement cde) { string name = cde.name; if (!"else".Equals(name) && !"{".Equals(name) && !"}".Equals(name)) { jumpLabels.Add(name, cde); } } //Console.WriteLine(cmd); } } List <DataElement> code = new List <DataElement>(); Dictionary <string, FuncDataElement> functions = new Dictionary <string, FuncDataElement>(); foreach (ScriptElement elem in flattened) { if (elem is FuncCallScriptElement) { string name = (elem as FuncCallScriptElement).name; ScriptElement[] parameters = (elem as FuncCallScriptElement).parameters; if (!functions.ContainsKey(name)) { functions[name] = new FuncDataElement(name); } functions[name].lastOffset = code.Count; code.Add(functions[name]); code.Add(new RawDataElement(parameters.Length)); foreach (ScriptElement param in parameters) { if (param is DataScriptElement) { code.Add((param as DataScriptElement).elem); } else { throw new Exception("Expected DataScriptElement, got " + param.GetType().Name); } } } else if (elem is DataScriptElement) { DataElement data = (elem as DataScriptElement).elem; if (data is ControlDataElement) { (data as ControlDataElement).codeOffset = code.Count; } code.Add(data); } else { } if (FlagCollection.current.Has('v')) { Console.WriteLine(elem); } } Console.ForegroundColor = ConsoleColor.Yellow; DataManager dataManager = new DataManager(); List <Tuple <int, int, int, int> > index = new List <Tuple <int, int, int, int> >(); List <int> codeData = new List <int>(); foreach (DataElement elem in code) { if (elem is ControlDataElement) { string name = (elem as ControlDataElement).name; if (jumpLabels.ContainsKey(name)) { (elem as ControlDataElement).link = jumpLabels[name]; } } if (elem is RawDataElement) { codeData.Add((elem as RawDataElement).value); } else { elem.WriteData(dataManager); Tuple <int, int, int, int> entry = elem.GetIndex(); if (FlagCollection.current.Has('v')) { Console.WriteLine(entry); } int i = index.IndexOf(entry); if (i == -1) { i = index.Count; index.Add(entry); } codeData.Add(i); } } Console.ResetColor(); BinaryWriter bw = new BinaryWriter(s); // write header s.Write(new byte[] { 0x59, 0x4B, 0x53, 0x30, 0x30, 0x31, 0x01, 0x00 }, 0, 8); s.Write(new byte[] { 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0, 8); bw.Write(0x30); bw.Write(codeData.Count); bw.Write(0x30 + codeData.Count * 4); bw.Write(index.Count); bw.Write(0x30 + codeData.Count * 4 + index.Count * 16); bw.Write(dataManager.offset); bw.Write(tempVarID); bw.Write(0); // write code sector foreach (int cmd in codeData) { bw.Write(cmd); } // write index foreach (Tuple <int, int, int, int> entry in index) { bw.Write(entry.Item1); bw.Write(entry.Item2); bw.Write(entry.Item3); bw.Write(entry.Item4); } // write data sector dataManager.WriteTo(s); return(s.Position - offset); }