Files
opensim/OpenSim/Tools/Compiler/Program.cs
Aleric Inglewood c90b986d8b Improved line map heuristics.
If the C# column can't be found in the positionMap (but the line can),
use the map immediately after it while correcting for the offset,
unless that results in an LSL position before the previous LSL position
in the positionMap.

The idea behind this heuristic is that in most, if not all cases C#
consumes more characters than LSL (for example LSL_Types.LSLInteger
instead of just 'integer').

Thus if the distance between the columns of two markers differ in
the C# and LSL file, the distance in the C# file will be larger.
Moreover, we can assume that every time this happens we will have
a marker at the beginning of the longer 'keyword', because those
keywords were generated by us in the first place.

For example:

C#:     LSL_Types.LSLInteger f2(LSL_Types.LSLString s)
        ^                       ^
        1                       2

will always have markers at the beginning of the long keywords
'LSL_Types.LSLInteger' and 'LSL_Types.LSLString'.
If an error is generated in between (for example at the beginning
of the function name 'f2') then the correct position is found
by using an offset relative to 2 rather than 1.

Note that a case where this isn't working correctly is
when the user adds extra spaces. For example:

LSL:   integer f2(    string s)

would still use the start of 'string' as reference and
then go backwards 3 characters only because the corresponding
C# still looks like

C#:     LSL_Types.LSLInteger f2(LSL_Types.LSLString s)
                             ^  ^
			     only 3 chars difference

and the reported error at 'f2' would be here:

LSL:   integer f2(    string s)
                   ^

This can only be fixed by generating a mapping for 'f2' itself, or
generating a mapping whenever the amount of spaces is changed.
2014-06-19 23:52:51 +01:00

326 lines
12 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.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.CSharp;
using OpenSim.Region.ScriptEngine.Shared.CodeTools;
using System.CodeDom.Compiler;
namespace OpenSim.Tools.LSL.Compiler
{
class Program
{
// Commented out because generated warning since m_positionMap could never be anything other than null
// private static Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>> m_positionMap;
private static CSharpCodeProvider CScodeProvider = new CSharpCodeProvider();
static void Main(string[] args)
{
string source = null;
if (args.Length == 0)
{
Console.WriteLine("No input file specified");
Environment.Exit(1);
}
if (!File.Exists(args[0]))
{
Console.WriteLine("Input file does not exist");
Environment.Exit(1);
}
try
{
ICodeConverter cvt = (ICodeConverter) new CSCodeGenerator();
source = cvt.Convert(File.ReadAllText(args[0]));
}
catch(Exception e)
{
Console.WriteLine("Conversion failed:\n"+e.Message);
Environment.Exit(1);
}
source = CreateCSCompilerScript(source);
try
{
Console.WriteLine(CompileFromDotNetText(source,"a.out"));
}
catch(Exception e)
{
Console.WriteLine("Conversion failed: "+e.Message);
Environment.Exit(1);
}
Environment.Exit(0);
}
private static string CreateCSCompilerScript(string compileScript)
{
compileScript = String.Empty +
"using OpenSim.Region.ScriptEngine.Shared; using System.Collections.Generic;\r\n" +
String.Empty + "namespace SecondLife { " +
String.Empty + "public class Script : OpenSim.Region.ScriptEngine.Shared.ScriptBase.ScriptBaseClass { \r\n" +
@"public Script() { } " +
compileScript +
"} }\r\n";
return compileScript;
}
private static string CompileFromDotNetText(string Script, string asset)
{
string OutFile = asset;
string disp ="OK";
try
{
File.Delete(OutFile);
}
catch (Exception e) // NOTLEGIT - Should be just FileIOException
{
throw new Exception("Unable to delete old existing "+
"script-file before writing new. Compile aborted: " +
e.ToString());
}
// Do actual compile
CompilerParameters parameters = new CompilerParameters();
parameters.IncludeDebugInformation = true;
string rootPath =
Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
parameters.ReferencedAssemblies.Add(Path.Combine(rootPath,
"OpenSim.Region.ScriptEngine.Shared.dll"));
parameters.ReferencedAssemblies.Add(Path.Combine(rootPath,
"OpenSim.Region.ScriptEngine.Shared.Api.Runtime.dll"));
parameters.GenerateExecutable = false;
parameters.OutputAssembly = OutFile;
parameters.IncludeDebugInformation = true;
parameters.WarningLevel = 1;
parameters.TreatWarningsAsErrors = false;
CompilerResults results = CScodeProvider.CompileAssemblyFromSource(parameters, Script);
if (results.Errors.Count > 0)
{
string errtext = String.Empty;
foreach (CompilerError CompErr in results.Errors)
{
string severity = CompErr.IsWarning ? "Warning" : "Error";
KeyValuePair<int, int> lslPos;
lslPos = FindErrorPosition(CompErr.Line, CompErr.Column);
string text = CompErr.ErrorText;
text = ReplaceTypes(CompErr.ErrorText);
// The Second Life viewer's script editor begins
// countingn lines and columns at 0, so we subtract 1.
errtext += String.Format("Line ({0},{1}): {4} {2}: {3}\n",
lslPos.Key - 1, lslPos.Value - 1,
CompErr.ErrorNumber, text, severity);
}
disp = "Completed with errors";
if (!File.Exists(OutFile))
{
throw new Exception(errtext);
}
}
if (!File.Exists(OutFile))
{
string errtext = String.Empty;
errtext += "No compile error. But not able to locate compiled file.";
throw new Exception(errtext);
}
// Because windows likes to perform exclusive locks, we simply
// write out a textual representation of the file here
//
// Read the binary file into a buffer
//
FileInfo fi = new FileInfo(OutFile);
if (fi == null)
{
string errtext = String.Empty;
errtext += "No compile error. But not able to stat file.";
throw new Exception(errtext);
}
Byte[] data = new Byte[fi.Length];
try
{
FileStream fs = File.Open(OutFile, FileMode.Open, FileAccess.Read);
fs.Read(data, 0, data.Length);
fs.Close();
}
catch (Exception)
{
string errtext = String.Empty;
errtext += "No compile error. But not able to open file.";
throw new Exception(errtext);
}
// Convert to base64
//
string filetext = System.Convert.ToBase64String(data);
Byte[] buf = Encoding.ASCII.GetBytes(filetext);
FileStream sfs = File.Create(OutFile + ".text");
sfs.Write(buf, 0, buf.Length);
sfs.Close();
string posmap = String.Empty;
// if (m_positionMap != null)
// {
// foreach (KeyValuePair<KeyValuePair<int, int>, KeyValuePair<int, int>> kvp in m_positionMap)
// {
// KeyValuePair<int, int> k = kvp.Key;
// KeyValuePair<int, int> v = kvp.Value;
// posmap += String.Format("{0},{1},{2},{3}\n",
// k.Key, k.Value, v.Key, v.Value);
// }
// }
buf = Encoding.ASCII.GetBytes(posmap);
FileStream mfs = File.Create(OutFile + ".map");
mfs.Write(buf, 0, buf.Length);
mfs.Close();
return disp;
}
private static string ReplaceTypes(string message)
{
message = message.Replace(
"OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString",
"string");
message = message.Replace(
"OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger",
"integer");
message = message.Replace(
"OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat",
"float");
message = message.Replace(
"OpenSim.Region.ScriptEngine.Shared.LSL_Types.list",
"list");
return message;
}
private static KeyValuePair<int, int> FindErrorPosition(int line, int col)
{
//return FindErrorPosition(line, col, m_positionMap);
return FindErrorPosition(line, col, null);
}
private class kvpSorter : IComparer<KeyValuePair<KeyValuePair<int, int>, KeyValuePair<int, int>>>
{
public int Compare(KeyValuePair<KeyValuePair<int, int>, KeyValuePair<int, int>> a,
KeyValuePair<KeyValuePair<int, int>, KeyValuePair<int, int>> b)
{
int kc = a.Key.Key.CompareTo(b.Key.Key);
return (kc != 0) ? kc : a.Key.Value.CompareTo(b.Key.Value);
}
}
public static KeyValuePair<int, int> FindErrorPosition(int line,
int col, Dictionary<KeyValuePair<int, int>,
KeyValuePair<int, int>> positionMap)
{
if (positionMap == null || positionMap.Count == 0)
return new KeyValuePair<int, int>(line, col);
KeyValuePair<int, int> ret = new KeyValuePair<int, int>();
if (positionMap.TryGetValue(new KeyValuePair<int, int>(line, col),
out ret))
return ret;
var sorted = new List<KeyValuePair<KeyValuePair<int, int>, KeyValuePair<int, int>>>(positionMap);
sorted.Sort(new kvpSorter());
int l = 1;
int c = 1;
int pl = 1;
foreach (KeyValuePair<KeyValuePair<int, int>, KeyValuePair<int, int>> posmap in sorted)
{
//m_log.DebugFormat("[Compiler]: Scanning line map {0},{1} --> {2},{3}", posmap.Key.Key, posmap.Key.Value, posmap.Value.Key, posmap.Value.Value);
int nl = posmap.Value.Key + line - posmap.Key.Key; // New, translated LSL line and column.
int nc = posmap.Value.Value + col - posmap.Key.Value;
// Keep going until we find the first point passed line,col.
if (posmap.Key.Key > line)
{
//m_log.DebugFormat("[Compiler]: Line is larger than requested {0},{1}, returning {2},{3}", line, col, l, c);
if (pl < line)
{
//m_log.DebugFormat("[Compiler]: Previous line ({0}) is less than requested line ({1}), setting column to 1.", pl, line);
c = 1;
}
break;
}
if (posmap.Key.Key == line && posmap.Key.Value > col)
{
// Never move l,c backwards.
if (nl > l || (nl == l && nc > c))
{
//m_log.DebugFormat("[Compiler]: Using offset relative to this: {0} + {1} - {2}, {3} + {4} - {5} = {6}, {7}",
// posmap.Value.Key, line, posmap.Key.Key, posmap.Value.Value, col, posmap.Key.Value, nl, nc);
l = nl;
c = nc;
}
//m_log.DebugFormat("[Compiler]: Column is larger than requested {0},{1}, returning {2},{3}", line, col, l, c);
break;
}
pl = posmap.Key.Key;
l = posmap.Value.Key;
c = posmap.Value.Value;
}
return new KeyValuePair<int, int>(l, c);
}
}
}