xn/Program.cs
Dominic Höglinger 01e08f1902 Update grammar, expand CRC32 function (#2)
XN grammar and transformation engine are updated, including support for bitwise operations, improved function parameter handling, and enhanced error reporting.

Reviewed-on: dominic/XNeedle#2
2026-04-25 10:26:05 +00:00

97 lines
3.7 KiB
C#

using System;
using System.Xml;
using System.Xml.Linq;
using CommandLine;
using Sprache;
using XNeedle.Parser;
using XNeedle.Parser.Elements;
using XNeedle.Transformation;
using XNeedle.Formats;
namespace XNeedle
{
class Program
{
public class Options
{
[Value(0, MetaName = "xn", Required = true, HelpText = "XN transformation definition")]
public required string XnDefinition { get; set; }
[Option('i', "input", HelpText = "Input XML file", Required = true)]
public required string Input { get; set; }
[Option('o', "output", HelpText = "Output XML file", Required = true)]
public required string Output { get; set; }
[Option('v', "verbose", Required = false, HelpText = "Set output to verbose messages.")]
public bool Verbose { get; set; }
}
static void Main(string[] args)
{
CommandLine.Parser.Default.ParseArguments<Options>(args)
.WithParsed(RunOptions)
.WithNotParsed(HandleParseError);
}
static void RunOptions(Options opts)
{
// Auto-discover operations via reflection
BangOperation.RegisterAll();
var rawSource = File.ReadAllText(opts.XnDefinition);
var xnSource = XnParser.StripComments(rawSource);
IEnumerable<Part> rules;
try
{
rules = XnParser.Rules.Parse(xnSource);
}
catch (ParseException ex)
{
var lines = rawSource.Split('\n');
int line = ex.Position?.Line ?? -1;
int col = ex.Position?.Column ?? -1;
bool showPosition = (line != -1) && (col != -1);
if (showPosition)
{
var msg = ex.Message;
var hint = (msg.Contains("expected }") && !msg.Contains("expected ';'"))
? "Hint: a missing ';' terminator may be located just after this position"
: "";
Console.Error.WriteLine($"{opts.XnDefinition}({line},{col}): parse error: {msg}");
var srcLine = line <= lines.Length ? lines[line - 1].TrimEnd() : "";
var pointer = new string(' ', Math.Max(0, col - 1)) + "^";
Console.Error.WriteLine($" {srcLine}");
Console.Error.WriteLine($" {pointer}");
if (!string.IsNullOrEmpty(hint))
{
Console.Error.WriteLine($"\n{hint}");
}
}
else
{
Console.Error.WriteLine($"{opts.XnDefinition}: parse error: {ex.Message}");
}
return;
}
var doc = XDocument.Load(opts.Input, LoadOptions.PreserveWhitespace);
var encoding = XmlRoundtripWriter.DetectEncoding(File.ReadAllBytes(opts.Input));
var sloc = new SourceLocation(opts.Input, encoding);
try
{
foreach (var rule in rules.OfType<Rule>())
rule.Transform(doc, sloc);
}
catch (RequireException ex)
{
Console.Error.WriteLine($"{ex.SLoc.FilePath}({ex.SLoc.LineNumber}): require error: {ex.UserMessage}");
return;
}
using var writer = XmlRoundtripWriter.Create(opts.Input, opts.Output);
doc.WriteTo(writer);
Console.WriteLine($"Applied \"{opts.XnDefinition}\" to \"{opts.Input}\" and saved result to \"{opts.Output}\"");
}
static void HandleParseError(IEnumerable<Error> errs) {}
}
}