OpenSimMirror/OpenSim/Region/ScriptEngine/Shared/CodeTools/CSCodeGenerator.cs

1173 lines
46 KiB
C#

/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using log4net;
using Tools;
using OpenSim.Region.Framework.Interfaces;
namespace OpenSim.Region.ScriptEngine.Shared.CodeTools
{
public class CSCodeGenerator : ICodeConverter
{
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static yyLSLSyntax yyLSL = new yyLSLSyntax();
private SYMBOL m_astRoot = null;
private Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>> m_positionMap;
private int m_braceCount; // for indentation
private int m_CSharpLine; // the current line of generated C# code
private int m_CSharpCol; // the current column of generated C# code
private List<string> m_warnings = new List<string>();
private IScriptModuleComms m_comms = null;
private bool m_insertCoopTerminationChecks;
private static string m_coopTerminationCheck = "opensim_reserved_CheckForCoopTermination();";
/// <summary>
/// Keep a record of the previous node when we do the parsing.
/// </summary>
/// <remarks>
/// We do this here because the parser generated by CSTools does not retain a reference to its parent node.
/// The previous node is required so we can correctly insert co-op termination checks when required.
/// </remarks>
// private SYMBOL m_previousNode;
/// <summary>
/// Creates an 'empty' CSCodeGenerator instance.
/// </summary>
public CSCodeGenerator()
{
m_comms = null;
ResetCounters();
}
public CSCodeGenerator(IScriptModuleComms comms, bool insertCoopTerminationChecks)
{
m_comms = comms;
m_insertCoopTerminationChecks = insertCoopTerminationChecks;
ResetCounters();
}
/// <summary>
/// Get the mapping between LSL and C# line/column number.
/// </summary>
/// <returns>Dictionary\<KeyValuePair\<int, int\>, KeyValuePair\<int, int\>\>.</returns>
public Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>> PositionMap
{
get { return m_positionMap; }
}
/// <summary>
/// Get the mapping between LSL and C# line/column number.
/// </summary>
/// <returns>SYMBOL pointing to root of the abstract syntax tree.</returns>
public SYMBOL ASTRoot
{
get { return m_astRoot; }
}
public void Clear()
{
m_astRoot.kids = null;
m_astRoot.yylx = null;
m_astRoot.yyps = null;
m_astRoot = null;
m_positionMap = null;
m_warnings.Clear();
m_comms = null;
}
/// <summary>
/// Resets various counters and metadata.
/// </summary>
private void ResetCounters()
{
m_braceCount = 0;
m_CSharpLine = 0;
m_CSharpCol = 1;
m_positionMap = new Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>>();
m_astRoot = null;
}
public string Convert(string script)
{
StringBuilder sb = new StringBuilder(4096);
Convert(script, sb);
return sb.ToString();
}
/// <summary>
/// Generate the code from the AST we have.
/// </summary>
/// <param name="script">The LSL source as a string.</param>
/// <returns>String containing the generated C# code.</returns>
public void Convert(string script, StringBuilder sb)
{
// m_log.DebugFormat("[CS CODE GENERATOR]: Converting to C#\n{0}", script);
m_warnings.Clear();
ResetCounters();
ErrorHandler errorHandler = new ErrorHandler(true);
Parser p = new LSLSyntax(yyLSL, errorHandler);
LSL2CSCodeTransformer codeTransformer;
try
{
codeTransformer = new LSL2CSCodeTransformer(p.Parse(script));
}
catch (CSToolsException e)
{
string message;
// LL start numbering lines at 0 - geeks!
// Also need to subtract one line we prepend!
//
string emessage = e.Message;
string slinfo = e.slInfo.ToString();
// Remove wrong line number info
//
if (emessage.StartsWith(slinfo+": "))
emessage = emessage.Substring(slinfo.Length+2);
message = String.Format("({0},{1}) {2}",
e.slInfo.lineNumber - 1,
e.slInfo.charPosition - 1, emessage);
throw new Exception(message);
}
m_astRoot = codeTransformer.Transform();
// standard preamble
m_braceCount++;
m_braceCount++;
// line number
m_CSharpLine += 10;
// here's the payload
sb.Append("\n");
foreach (SYMBOL s in m_astRoot.kids)
GenerateNodeToSB(m_astRoot, s, sb);
codeTransformer = null;
p.m_lexer.m_buf=null;
p.m_lexer.yytext = null;
p.m_lexer = null;
p.m_symbols = null;
p = null;
errorHandler = null;
// close braces!
// m_braceCount--;
//retstr += GenerateIndentedLine("}");
// m_braceCount--;
//retstr += GenerateLine("}");
// Removes all carriage return characters which may be generated in Windows platform. Is there
// cleaner way of doing this?
// sb.Replace("\r", "");
}
/// <summary>
/// Get the set of warnings generated during compilation.
/// </summary>
/// <returns></returns>
public string[] GetWarnings()
{
return m_warnings.ToArray();
}
private void AddWarning(string warning)
{
if (!m_warnings.Contains(warning))
{
m_warnings.Add(warning);
}
}
/// <summary>
/// Recursively called to generate each type of node. Will generate this
/// node, then all it's children.
/// </summary>
/// <param name="previousSymbol">The parent node.</param>
/// <param name="s">The current node to generate code for.</param>
/// <returns>String containing C# code for SYMBOL s.</returns>
private void GenerateNodeToSB(SYMBOL previousSymbol, SYMBOL s, StringBuilder sb)
{
// make sure to put type lower in the inheritance hierarchy first
// ie: since IdentArgument and ExpressionArgument inherit from
// Argument, put IdentArgument and ExpressionArgument before Argument
if (s is GlobalFunctionDefinition)
GenerateGlobalFunctionDefinition((GlobalFunctionDefinition) s, sb);
else if (s is GlobalVariableDeclaration)
GenerateGlobalVariableDeclaration((GlobalVariableDeclaration) s , sb);
else if (s is State)
GenerateState((State) s, sb);
else if (s is CompoundStatement)
GenerateCompoundStatement(previousSymbol, (CompoundStatement) s, sb);
else if (s is Declaration)
GenerateDeclaration((Declaration) s, sb);
else if (s is Statement)
GenerateStatement(previousSymbol, (Statement) s, sb);
else if (s is ReturnStatement)
GenerateReturnStatement((ReturnStatement) s, sb);
else if (s is JumpLabel)
GenerateJumpLabel((JumpLabel) s, sb);
else if (s is JumpStatement)
GenerateJumpStatement((JumpStatement) s, sb);
else if (s is StateChange)
GenerateStateChange((StateChange) s, sb);
else if (s is IfStatement)
GenerateIfStatement((IfStatement) s, sb);
else if (s is WhileStatement)
GenerateWhileStatement((WhileStatement) s, sb);
else if (s is DoWhileStatement)
GenerateDoWhileStatement((DoWhileStatement) s, sb);
else if (s is ForLoop)
GenerateForLoop((ForLoop) s, sb);
else if (s is ArgumentList)
GenerateArgumentList((ArgumentList) s, sb);
else if (s is Assignment)
GenerateAssignment((Assignment) s, sb);
else if (s is BinaryExpression)
GenerateBinaryExpression((BinaryExpression) s, sb);
else if (s is ParenthesisExpression)
GenerateParenthesisExpression((ParenthesisExpression) s, sb);
else if (s is UnaryExpression)
GenerateUnaryExpression((UnaryExpression) s, sb);
else if (s is IncrementDecrementExpression)
GenerateIncrementDecrementExpression((IncrementDecrementExpression) s, sb);
else if (s is TypecastExpression)
GenerateTypecastExpression((TypecastExpression) s, sb);
else if (s is FunctionCall)
GenerateFunctionCall((FunctionCall) s, sb);
else if (s is VectorConstant)
GenerateVectorConstant((VectorConstant) s, sb);
else if (s is RotationConstant)
GenerateRotationConstant((RotationConstant) s, sb);
else if (s is ListConstant)
GenerateListConstant((ListConstant) s, sb);
else if (s is Constant)
GenerateConstant((Constant) s, sb);
else if (s is IdentDotExpression)
Generate(CheckName(((IdentDotExpression) s).Name) + "." + ((IdentDotExpression) s).Member, s, sb);
else if (s is IdentExpression)
GenerateIdentifier(((IdentExpression) s).Name, s, sb);
else if (s is IDENT)
Generate(CheckName(((TOKEN) s).yytext), s, sb);
else
{
foreach (SYMBOL kid in s.kids)
GenerateNodeToSB(s, kid,sb);
}
return;
}
/// <summary>
/// Generates the code for a GlobalFunctionDefinition node.
/// </summary>
/// <param name="gf">The GlobalFunctionDefinition node.</param>
/// <returns>String containing C# code for GlobalFunctionDefinition gf.</returns>
private void GenerateGlobalFunctionDefinition(GlobalFunctionDefinition gf, StringBuilder sb)
{
// we need to separate the argument declaration list from other kids
List<SYMBOL> argumentDeclarationListKids = new List<SYMBOL>();
List<SYMBOL> remainingKids = new List<SYMBOL>();
foreach (SYMBOL kid in gf.kids)
if (kid is ArgumentDeclarationList)
argumentDeclarationListKids.Add(kid);
else
remainingKids.Add(kid);
GenerateIndented(String.Format("{0} {1}(", gf.ReturnType, CheckName(gf.Name)), gf, sb);
// print the state arguments, if any
foreach (SYMBOL kid in argumentDeclarationListKids)
GenerateArgumentDeclarationList((ArgumentDeclarationList) kid, sb);
GenerateLine(")", sb);
foreach (SYMBOL kid in remainingKids)
GenerateNodeToSB(gf, kid,sb);
}
/// <summary>
/// Generates the code for a GlobalVariableDeclaration node.
/// </summary>
/// <param name="gv">The GlobalVariableDeclaration node.</param>
/// <returns>String containing C# code for GlobalVariableDeclaration gv.</returns>
private void GenerateGlobalVariableDeclaration(GlobalVariableDeclaration gv, StringBuilder sb)
{
foreach (SYMBOL s in gv.kids)
{
Indent(sb);
GenerateNodeToSB(gv, s ,sb);
GenerateLine(";", sb);
}
}
/// <summary>
/// Generates the code for a State node.
/// </summary>
/// <param name="s">The State node.</param>
/// <returns>String containing C# code for State s.</returns>
private void GenerateState(State s, StringBuilder sb)
{
foreach (SYMBOL kid in s.kids)
if (kid is StateEvent)
GenerateStateEvent((StateEvent) kid, s.Name, sb);
}
/// <summary>
/// Generates the code for a StateEvent node.
/// </summary>
/// <param name="se">The StateEvent node.</param>
/// <param name="parentStateName">The name of the parent state.</param>
/// <returns>String containing C# code for StateEvent se.</returns>
private void GenerateStateEvent(StateEvent se, string parentStateName, StringBuilder sb)
{
// we need to separate the argument declaration list from other kids
List<SYMBOL> argumentDeclarationListKids = new List<SYMBOL>();
List<SYMBOL> remainingKids = new List<SYMBOL>();
foreach (SYMBOL kid in se.kids)
if (kid is ArgumentDeclarationList)
argumentDeclarationListKids.Add(kid);
else
remainingKids.Add(kid);
// "state" (function) declaration
GenerateIndented(String.Format("public void {0}_event_{1}(", parentStateName, se.Name), se , sb);
// print the state arguments, if any
foreach (SYMBOL kid in argumentDeclarationListKids)
GenerateArgumentDeclarationList((ArgumentDeclarationList) kid, sb);
GenerateLine(")", sb);
foreach (SYMBOL kid in remainingKids)
GenerateNodeToSB(se, kid, sb);
}
/// <summary>
/// Generates the code for an ArgumentDeclarationList node.
/// </summary>
/// <param name="adl">The ArgumentDeclarationList node.</param>
/// <returns>String containing C# code for ArgumentDeclarationList adl.</returns>
private void GenerateArgumentDeclarationList(ArgumentDeclarationList adl, StringBuilder sb)
{
int comma = adl.kids.Count - 1; // tells us whether to print a comma
foreach (Declaration d in adl.kids)
{
Generate(String.Format("{0} {1}", d.Datatype, CheckName(d.Id)), d, sb);
if (0 < comma--)
Generate(", ", sb);
}
}
/// <summary>
/// Generates the code for an ArgumentList node.
/// </summary>
/// <param name="al">The ArgumentList node.</param>
/// <returns>String containing C# code for ArgumentList al.</returns>
private void GenerateArgumentList(ArgumentList al, StringBuilder sb)
{
int comma = al.kids.Count - 1; // tells us whether to print a comma
foreach (SYMBOL s in al.kids)
{
GenerateNodeToSB(al, s, sb);
if (0 < comma--)
Generate(", ", sb);
}
}
/// <summary>
/// Generates the code for a CompoundStatement node.
/// </summary>
/// <param name="cs">The CompoundStatement node.</param>
/// <returns>String containing C# code for CompoundStatement cs.</returns>
private void GenerateCompoundStatement(SYMBOL previousSymbol, CompoundStatement cs, StringBuilder sb)
{
// opening brace
GenerateIndentedLine("{", sb);
m_braceCount++;
if (m_insertCoopTerminationChecks)
{
// We have to check in event functions as well because the user can manually call these.
if (previousSymbol is GlobalFunctionDefinition
|| previousSymbol is WhileStatement
|| previousSymbol is DoWhileStatement
|| previousSymbol is ForLoop
|| previousSymbol is StateEvent)
GenerateIndentedLine(m_coopTerminationCheck, sb);
}
foreach (SYMBOL kid in cs.kids)
GenerateNodeToSB(cs, kid, sb);
// closing brace
m_braceCount--;
GenerateIndentedLine("}", sb);
}
/// <summary>
/// Generates the code for a Declaration node.
/// </summary>
/// <param name="d">The Declaration node.</param>
/// <returns>String containing C# code for Declaration d.</returns>
private void GenerateDeclaration(Declaration d, StringBuilder sb)
{
Generate(String.Format("{0} {1}", d.Datatype, CheckName(d.Id)), d, sb);
}
/// <summary>
/// Generates the code for a Statement node.
/// </summary>
/// <param name="s">The Statement node.</param>
/// <returns>String containing C# code for Statement s.</returns>
private void GenerateStatement(SYMBOL previousSymbol, Statement s, StringBuilder sb)
{
string retstr = String.Empty;
bool printSemicolon = true;
bool transformToBlock = false;
if (m_insertCoopTerminationChecks)
{
// A non-braced single line do while structure cannot contain multiple statements.
// So to insert the termination check we change this to a braced control structure instead.
if (previousSymbol is WhileStatement
|| previousSymbol is DoWhileStatement
|| previousSymbol is ForLoop)
{
transformToBlock = true;
// FIXME: This will be wrongly indented because the previous for/while/dowhile will have already indented.
GenerateIndentedLine("{", sb);
GenerateIndentedLine(m_coopTerminationCheck, sb);
}
}
Indent(sb);
if (0 < s.kids.Count)
{
// Jump label prints its own colon, we don't need a semicolon.
printSemicolon = !(s.kids.Top is JumpLabel);
// If we encounter a lone Ident, we skip it, since that's a C#
// (MONO) error.
if (!(s.kids.Top is IdentExpression && 1 == s.kids.Count))
foreach (SYMBOL kid in s.kids)
GenerateNodeToSB(s, kid, sb);
}
if (printSemicolon)
GenerateLine(";", sb);
if (transformToBlock)
{
// FIXME: This will be wrongly indented because the for/while/dowhile is currently handling the unindent
GenerateIndentedLine("}", sb);
}
}
/// <summary>
/// Generates the code for an Assignment node.
/// </summary>
/// <param name="a">The Assignment node.</param>
/// <returns>String containing C# code for Assignment a.</returns>
private void GenerateAssignment(Assignment a, StringBuilder sb)
{
List<string> identifiers = new List<string>();
checkForMultipleAssignments(identifiers, a);
GenerateNodeToSB(a, (SYMBOL) a.kids.Pop(), sb);
Generate(String.Format(" {0} ", a.AssignmentType), a, sb);
foreach (SYMBOL kid in a.kids)
GenerateNodeToSB(a, kid, sb);
}
// This code checks for LSL of the following forms, and generates a
// warning if it finds them.
//
// list l = [ "foo" ];
// l = (l=[]) + l + ["bar"];
// (produces l=["foo","bar"] in SL but l=["bar"] in OS)
//
// integer i;
// integer j;
// i = (j = 3) + (j = 4) + (j = 5);
// (produces j=3 in SL but j=5 in OS)
//
// Without this check, that code passes compilation, but does not do what
// the end user expects, because LSL in SL evaluates right to left instead
// of left to right.
//
// The theory here is that producing an error and alerting the end user that
// something needs to change is better than silently generating incorrect code.
private void checkForMultipleAssignments(List<string> identifiers, SYMBOL s)
{
if (s is Assignment)
{
Assignment a = (Assignment)s;
string newident = null;
if (a.kids[0] is Declaration)
{
newident = ((Declaration)a.kids[0]).Id;
}
else if (a.kids[0] is IDENT)
{
newident = ((IDENT)a.kids[0]).yytext;
}
else if (a.kids[0] is IdentDotExpression)
{
newident = ((IdentDotExpression)a.kids[0]).Name; // +"." + ((IdentDotExpression)a.kids[0]).Member;
}
else
{
AddWarning(String.Format("Multiple assignments checker internal error '{0}' at line {1} column {2}.", a.kids[0].GetType(), ((SYMBOL)a.kids[0]).Line - 1, ((SYMBOL)a.kids[0]).Position));
}
if (identifiers.Contains(newident))
{
AddWarning(String.Format("Multiple assignments to '{0}' at line {1} column {2}; results may differ between LSL and OSSL.", newident, ((SYMBOL)a.kids[0]).Line - 1, ((SYMBOL)a.kids[0]).Position));
}
identifiers.Add(newident);
}
int index;
for (index = 0; index < s.kids.Count; index++)
{
checkForMultipleAssignments(identifiers, (SYMBOL) s.kids[index]);
}
}
/// <summary>
/// Generates the code for a ReturnStatement node.
/// </summary>
/// <param name="rs">The ReturnStatement node.</param>
/// <returns>String containing C# code for ReturnStatement rs.</returns>
private void GenerateReturnStatement(ReturnStatement rs, StringBuilder sb)
{
Generate("return ", rs, sb);
foreach (SYMBOL kid in rs.kids)
GenerateNodeToSB(rs, kid, sb);
}
/// <summary>
/// Generates the code for a JumpLabel node.
/// </summary>
/// <param name="jl">The JumpLabel node.</param>
/// <returns>String containing C# code for JumpLabel jl.</returns>
private void GenerateJumpLabel(JumpLabel jl, StringBuilder sb)
{
string labelStatement;
if (m_insertCoopTerminationChecks)
labelStatement = m_coopTerminationCheck;
else
labelStatement = "NoOp();";
GenerateLine(String.Format("{0}: {1}", CheckName(jl.LabelName), labelStatement), jl, sb);
}
/// <summary>
/// Generates the code for a JumpStatement node.
/// </summary>
/// <param name="js">The JumpStatement node.</param>
/// <returns>String containing C# code for JumpStatement js.</returns>
private void GenerateJumpStatement(JumpStatement js, StringBuilder sb)
{
Generate(String.Format("goto {0}", CheckName(js.TargetName)), js, sb);
}
/// <summary>
/// Generates the code for an IfStatement node.
/// </summary>
/// <param name="ifs">The IfStatement node.</param>
/// <returns>String containing C# code for IfStatement ifs.</returns>
private void GenerateIfStatement(IfStatement ifs, StringBuilder sb)
{
GenerateIndented("if (", ifs, sb);
GenerateNodeToSB(ifs, (SYMBOL) ifs.kids.Pop(), sb);
GenerateLine(")", sb);
// CompoundStatement handles indentation itself but we need to do it
// otherwise.
bool indentHere = ifs.kids.Top is Statement;
if (indentHere) m_braceCount++;
GenerateNodeToSB(ifs, (SYMBOL) ifs.kids.Pop(), sb);
if (indentHere) m_braceCount--;
if (0 < ifs.kids.Count) // do it again for an else
{
GenerateIndentedLine("else", ifs, sb);
indentHere = ifs.kids.Top is Statement;
if (indentHere) m_braceCount++;
GenerateNodeToSB(ifs, (SYMBOL) ifs.kids.Pop(), sb);
if (indentHere) m_braceCount--;
}
}
/// <summary>
/// Generates the code for a StateChange node.
/// </summary>
/// <param name="sc">The StateChange node.</param>
/// <returns>String containing C# code for StateChange sc.</returns>
private void GenerateStateChange(StateChange sc, StringBuilder sb)
{
Generate(String.Format("state(\"{0}\")", sc.NewState), sc, sb);
}
/// <summary>
/// Generates the code for a WhileStatement node.
/// </summary>
/// <param name="ws">The WhileStatement node.</param>
/// <returns>String containing C# code for WhileStatement ws.</returns>
private void GenerateWhileStatement(WhileStatement ws, StringBuilder sb)
{
GenerateIndented("while (", ws, sb);
GenerateNodeToSB(ws, (SYMBOL) ws.kids.Pop(), sb);
GenerateLine(")", sb);
// CompoundStatement handles indentation itself but we need to do it
// otherwise.
bool indentHere = ws.kids.Top is Statement;
if (indentHere) m_braceCount++;
GenerateNodeToSB(ws, (SYMBOL) ws.kids.Pop(), sb);
if (indentHere) m_braceCount--;
}
/// <summary>
/// Generates the code for a DoWhileStatement node.
/// </summary>
/// <param name="dws">The DoWhileStatement node.</param>
/// <returns>String containing C# code for DoWhileStatement dws.</returns>
private void GenerateDoWhileStatement(DoWhileStatement dws, StringBuilder sb)
{
GenerateIndentedLine("do", dws, sb);
// CompoundStatement handles indentation itself but we need to do it
// otherwise.
bool indentHere = dws.kids.Top is Statement;
if (indentHere) m_braceCount++;
GenerateNodeToSB(dws, (SYMBOL) dws.kids.Pop(), sb);
if (indentHere) m_braceCount--;
GenerateIndented("while (", dws ,sb);
GenerateNodeToSB(dws, (SYMBOL) dws.kids.Pop(), sb);
GenerateLine(");", sb);
}
/// <summary>
/// Generates the code for a ForLoop node.
/// </summary>
/// <param name="fl">The ForLoop node.</param>
/// <returns>String containing C# code for ForLoop fl.</returns>
private void GenerateForLoop(ForLoop fl, StringBuilder sb)
{
GenerateIndented("for (", fl, sb);
// It's possible that we don't have an assignment, in which case
// the child will be null and we only print the semicolon.
// for (x = 0; x < 10; x++)
// ^^^^^
ForLoopStatement s = (ForLoopStatement) fl.kids.Pop();
if (null != s)
{
GenerateForLoopStatement(s, sb);
}
Generate("; ", sb);
// for (x = 0; x < 10; x++)
// ^^^^^^
GenerateNodeToSB(fl, (SYMBOL) fl.kids.Pop(), sb);
Generate("; ", sb);
// for (x = 0; x < 10; x++)
// ^^^
GenerateForLoopStatement((ForLoopStatement) fl.kids.Pop(), sb);
GenerateLine(")", sb);
// CompoundStatement handles indentation itself but we need to do it
// otherwise.
bool indentHere = fl.kids.Top is Statement;
if (indentHere) m_braceCount++;
GenerateNodeToSB(fl, (SYMBOL) fl.kids.Pop(), sb);
if (indentHere) m_braceCount--;
}
/// <summary>
/// Generates the code for a ForLoopStatement node.
/// </summary>
/// <param name="fls">The ForLoopStatement node.</param>
/// <returns>String containing C# code for ForLoopStatement fls.</returns>
private void GenerateForLoopStatement(ForLoopStatement fls, StringBuilder sb)
{
int comma = fls.kids.Count - 1; // tells us whether to print a comma
// It's possible that all we have is an empty Ident, for example:
//
// for (x; x < 10; x++) { ... }
//
// Which is illegal in C# (MONO). We'll skip it.
if (fls.kids.Top is IdentExpression && 1 == fls.kids.Count)
return;
for (int i = 0; i < fls.kids.Count; i++)
{
SYMBOL s = (SYMBOL)fls.kids[i];
// Statements surrounded by parentheses in for loops
//
// e.g. for ((i = 0), (j = 7); (i < 10); (++i))
//
// are legal in LSL but not in C# so we need to discard the parentheses
//
// The following, however, does not appear to be legal in LLS
//
// for ((i = 0, j = 7); (i < 10); (++i))
//
// As of Friday 20th November 2009, the Linden Lab simulators appear simply never to compile or run this
// script but with no debug or warnings at all! Therefore, we won't deal with this yet (which looks
// like it would be considerably more complicated to handle).
while (s is ParenthesisExpression)
s = (SYMBOL)s.kids.Pop();
GenerateNodeToSB(fls, s, sb);
if (0 < comma--)
Generate(", ", sb);
}
}
/// <summary>
/// Generates the code for a BinaryExpression node.
/// </summary>
/// <param name="be">The BinaryExpression node.</param>
/// <returns>String containing C# code for BinaryExpression be.</returns>
private void GenerateBinaryExpression(BinaryExpression be, StringBuilder sb)
{
if (be.ExpressionSymbol.Equals("&&") || be.ExpressionSymbol.Equals("||"))
{
// special case handling for logical and/or, see Mantis 3174
sb.Append("((bool)(");
GenerateNodeToSB(be, (SYMBOL)be.kids.Pop(), sb);
sb.Append("))");
Generate(String.Format(" {0} ", be.ExpressionSymbol.Substring(0,1)), be, sb);
sb.Append("((bool)(");
foreach (SYMBOL kid in be.kids)
GenerateNodeToSB(be, kid, sb);
sb.Append("))");
}
else
{
GenerateNodeToSB(be, (SYMBOL)be.kids.Pop(), sb);
Generate(String.Format(" {0} ", be.ExpressionSymbol), be, sb);
foreach (SYMBOL kid in be.kids)
GenerateNodeToSB(be, kid, sb);
}
}
/// <summary>
/// Generates the code for a UnaryExpression node.
/// </summary>
/// <param name="ue">The UnaryExpression node.</param>
/// <returns>String containing C# code for UnaryExpression ue.</returns>
private void GenerateUnaryExpression(UnaryExpression ue, StringBuilder sb)
{
Generate(ue.UnarySymbol, ue, sb);
GenerateNodeToSB(ue, (SYMBOL) ue.kids.Pop(), sb);
}
/// <summary>
/// Generates the code for a ParenthesisExpression node.
/// </summary>
/// <param name="pe">The ParenthesisExpression node.</param>
/// <returns>String containing C# code for ParenthesisExpression pe.</returns>
private void GenerateParenthesisExpression(ParenthesisExpression pe, StringBuilder sb)
{
string retstr = String.Empty;
Generate("(", sb);
foreach (SYMBOL kid in pe.kids)
GenerateNodeToSB(pe, kid, sb);
Generate(")", sb);
}
/// <summary>
/// Generates the code for a IncrementDecrementExpression node.
/// </summary>
/// <param name="ide">The IncrementDecrementExpression node.</param>
/// <returns>String containing C# code for IncrementDecrementExpression ide.</returns>
private void GenerateIncrementDecrementExpression(IncrementDecrementExpression ide, StringBuilder sb)
{
if (0 < ide.kids.Count)
{
IdentDotExpression dot = (IdentDotExpression) ide.kids.Top;
Generate(String.Format("{0}", ide.PostOperation ? CheckName(dot.Name) + "." + dot.Member + ide.Operation : ide.Operation + CheckName(dot.Name) + "." + dot.Member), ide, sb);
}
else
Generate(String.Format("{0}", ide.PostOperation ? CheckName(ide.Name) + ide.Operation : ide.Operation + CheckName(ide.Name)), ide, sb);
}
/// <summary>
/// Generates the code for a TypecastExpression node.
/// </summary>
/// <param name="te">The TypecastExpression node.</param>
/// <returns>String containing C# code for TypecastExpression te.</returns>
private void GenerateTypecastExpression(TypecastExpression te, StringBuilder sb)
{
// we wrap all typecasted statements in parentheses
Generate(String.Format("({0}) (", te.TypecastType), te, sb);
GenerateNodeToSB(te, (SYMBOL) te.kids.Pop(), sb);
Generate(")", sb);
}
/// <summary>
/// Generates the code for an identifier
/// </summary>
/// <param name="id">The symbol name</param>
/// <param name="s">The Symbol node.</param>
/// <returns>String containing C# code for identifier reference.</returns>
private void GenerateIdentifier(string id, SYMBOL s, StringBuilder sb)
{
if (m_comms != null)
{
object value = m_comms.LookupModConstant(id);
if (value != null)
{
string retval = null;
if (value is int)
retval = String.Format("new LSL_Types.LSLInteger({0})",((int)value).ToString());
else if (value is float)
retval = String.Format("new LSL_Types.LSLFloat({0})",((float)value).ToString());
else if (value is string)
retval = String.Format("new LSL_Types.LSLString(\"{0}\")",((string)value));
else if (value is OpenMetaverse.UUID)
retval = String.Format("new LSL_Types.key(\"{0}\")",((OpenMetaverse.UUID)value).ToString());
else if (value is OpenMetaverse.Vector3)
retval = String.Format("new LSL_Types.Vector3(\"{0}\")",((OpenMetaverse.Vector3)value).ToString());
else if (value is OpenMetaverse.Quaternion)
retval = String.Format("new LSL_Types.Quaternion(\"{0}\")",((OpenMetaverse.Quaternion)value).ToString());
else retval = id;
Generate(retval, s, sb);
return;
}
}
Generate(CheckName(id), s, sb);
return;
}
/// <summary>
/// Generates the code for a FunctionCall node.
/// </summary>
/// <param name="fc">The FunctionCall node.</param>
/// <returns>String containing C# code for FunctionCall fc.</returns>
private void GenerateFunctionCall(FunctionCall fc, StringBuilder sb)
{
string modinvoke = null;
if (m_comms != null)
modinvoke = m_comms.LookupModInvocation(fc.Id);
if (modinvoke != null)
{
if (fc.kids[0] is ArgumentList)
{
if ((fc.kids[0] as ArgumentList).kids.Count == 0)
Generate(String.Format("{0}(\"{1}\"",modinvoke,fc.Id), fc, sb);
else
Generate(String.Format("{0}(\"{1}\",",modinvoke,fc.Id), fc, sb);
}
}
else
{
Generate(String.Format("{0}(", CheckName(fc.Id)), fc, sb);
}
foreach (SYMBOL kid in fc.kids)
GenerateNodeToSB(fc, kid, sb);
Generate(")", sb);
}
/// <summary>
/// Generates the code for a Constant node.
/// </summary>
/// <param name="c">The Constant node.</param>
/// <returns>String containing C# code for Constant c.</returns>
private void GenerateConstant(Constant c, StringBuilder sb)
{
// Supprt LSL's weird acceptance of floats with no trailing digits
// after the period. Turn float x = 10.; into float x = 10.0;
if ("LSL_Types.LSLFloat" == c.Type)
{
int dotIndex = c.Value.IndexOf('.') + 1;
if (0 < dotIndex && (dotIndex == c.Value.Length || !Char.IsDigit(c.Value[dotIndex])))
c.Value = c.Value.Insert(dotIndex, "0");
c.Value = "new LSL_Types.LSLFloat("+c.Value+")";
}
else if ("LSL_Types.LSLInteger" == c.Type)
{
c.Value = "new LSL_Types.LSLInteger("+c.Value+")";
}
else if ("LSL_Types.LSLString" == c.Type)
{
c.Value = "new LSL_Types.LSLString(\""+c.Value+"\")";
}
else if ("LSL_Types.key" == c.Type)
{
c.Value = "new LSL_Types.key(\""+c.Value+"\")";
}
Generate(c.Value, c, sb);
}
/// <summary>
/// Generates the code for a VectorConstant node.
/// </summary>
/// <param name="vc">The VectorConstant node.</param>
/// <returns>String containing C# code for VectorConstant vc.</returns>
private void GenerateVectorConstant(VectorConstant vc, StringBuilder sb)
{
Generate(String.Format("new {0}(", vc.Type), vc, sb);
GenerateNodeToSB(vc, (SYMBOL) vc.kids.Pop(), sb);
Generate(", ", sb);
GenerateNodeToSB(vc, (SYMBOL) vc.kids.Pop(), sb);
Generate(", ", sb);
GenerateNodeToSB(vc, (SYMBOL) vc.kids.Pop(), sb);
Generate(")", sb);
}
/// <summary>
/// Generates the code for a RotationConstant node.
/// </summary>
/// <param name="rc">The RotationConstant node.</param>
/// <returns>String containing C# code for RotationConstant rc.</returns>
private void GenerateRotationConstant(RotationConstant rc, StringBuilder sb)
{
Generate(String.Format("new {0}(", rc.Type), rc, sb);
GenerateNodeToSB(rc, (SYMBOL) rc.kids.Pop(), sb);
Generate(", ", sb);
GenerateNodeToSB(rc, (SYMBOL) rc.kids.Pop(), sb);
Generate(", ", sb);
GenerateNodeToSB(rc, (SYMBOL) rc.kids.Pop(), sb);
Generate(", ", sb);
GenerateNodeToSB(rc, (SYMBOL) rc.kids.Pop(), sb);
Generate(")", sb);
}
/// <summary>
/// Generates the code for a ListConstant node.
/// </summary>
/// <param name="lc">The ListConstant node.</param>
/// <returns>String containing C# code for ListConstant lc.</returns>
private void GenerateListConstant(ListConstant lc, StringBuilder sb)
{
Generate(String.Format("new {0}(", lc.Type), lc, sb);
foreach (SYMBOL kid in lc.kids)
GenerateNodeToSB(lc, kid, sb);
Generate(")", sb);
}
/// <summary>
/// Prints a newline.
/// </summary>
/// <returns>A newline.</returns>
private void GenerateLine(StringBuilder sb)
{
sb.Append("\n");
m_CSharpLine++;
m_CSharpCol = 1;
}
/// <summary>
/// Prints text, followed by a newline.
/// </summary>
/// <param name="s">String of text to print.</param>
/// <returns>String s followed by newline.</returns>
private void GenerateLine(string s, StringBuilder sb)
{
sb.Append(s);
sb.Append("\n");
m_CSharpLine++;
m_CSharpCol = 1;
}
/// <summary>
/// Prints text, followed by a newline.
/// </summary>
/// <param name="s">String of text to print.</param>
/// <param name="sym">Symbol being generated to extract original line
/// number and column from.</param>
/// <returns>String s followed by newline.</returns>
private void GenerateLine(string s, SYMBOL sym, StringBuilder sb)
{
Generate(s, sym, sb);
sb.Append("\n");
m_CSharpLine++;
m_CSharpCol = 1;
}
/// <summary>
/// Prints text.
/// </summary>
/// <param name="s">String of text to print.</param>
/// <returns>String s.</returns>
private void Generate(string s, StringBuilder sb)
{
sb.Append(s);
m_CSharpCol += s.Length;
}
/// <summary>
/// Prints text.
/// </summary>
/// <param name="s">String of text to print.</param>
/// <param name="sym">Symbol being generated to extract original line
/// number and column from.</param>
/// <returns>String s.</returns>
private void Generate(string s, SYMBOL sym, StringBuilder sb)
{
sb.Append(s);
if (null != sym)
m_positionMap.Add(new KeyValuePair<int, int>(m_CSharpLine, m_CSharpCol), new KeyValuePair<int, int>(sym.Line, sym.Position));
m_CSharpCol += s.Length;
}
/// <summary>
/// Prints text correctly indented, followed by a newline.
/// </summary>
/// <param name="s">String of text to print.</param>
/// <returns>Properly indented string s followed by newline.</returns>
private void GenerateIndentedLine(string s, StringBuilder sb)
{
GenerateIndentedLine(s, null, sb);
}
/// <summary>
/// Prints text correctly indented, followed by a newline.
/// </summary>
/// <param name="s">String of text to print.</param>
/// <param name="sym">Symbol being generated to extract original line
/// number and column from.</param>
/// <returns>Properly indented string s followed by newline.</returns>
private void GenerateIndentedLine(string s, SYMBOL sym, StringBuilder sb)
{
GenerateIndented(s, sym , sb );
sb.Append("\n");
m_CSharpLine++;
m_CSharpCol = 1;
}
/// <summary>
/// Prints text correctly indented.
/// </summary>
/// <param name="s">String of text to print.</param>
/// <returns>Properly indented string s.</returns>
//private string GenerateIndented(string s)
//{
// return GenerateIndented(s, null);
//}
// THIS FUNCTION IS COMMENTED OUT TO SUPPRESS WARNINGS
/// <summary>
/// Prints text correctly indented.
/// </summary>
/// <param name="s">String of text to print.</param>
/// <param name="sym">Symbol being generated to extract original line
/// number and column from.</param>
/// <returns>Properly indented string s.</returns>
private void GenerateIndented(string s, SYMBOL sym, StringBuilder sb)
{
Indent(sb);
sb.Append(s);
if (null != sym)
m_positionMap.Add(new KeyValuePair<int, int>(m_CSharpLine, m_CSharpCol), new KeyValuePair<int, int>(sym.Line, sym.Position));
m_CSharpCol += s.Length;
}
/// <summary>
/// Prints correct indentation.
/// </summary>
/// <returns>Indentation based on brace count.</returns>
private void Indent(StringBuilder sb)
{
for (int i = 0; i < m_braceCount; i++)
{
sb.Append(" ");
m_CSharpCol += 4;
}
}
/// <summary>
/// Returns the passed name with an underscore prepended if that name is a reserved word in C#
/// and not resevered in LSL otherwise it just returns the passed name.
///
/// This makes no attempt to cache the results to minimise future lookups. For a non trivial
/// scripts the number of unique identifiers could easily grow to the size of the reserved word
/// list so maintaining a list or dictionary and doing the lookup there firstwould probably not
/// give any real speed advantage.
///
/// I believe there is a class Microsoft.CSharp.CSharpCodeProvider that has a function
/// CreateValidIdentifier(str) that will return either the value of str if it is not a C#
/// key word or "_"+str if it is. But availability under Mono?
/// </summary>
private string CheckName(string s)
{
if (CSReservedWords.IsReservedWord(s))
return "@" + s;
else
return s;
}
}
}