Merge MVP features #1

Merged
dominic merged 16 commits from prototype into master 2026-04-24 18:48:16 +00:00
6 changed files with 121 additions and 1 deletions
Showing only changes of commit f4a563a9d4 - Show all commits

View File

@ -25,5 +25,7 @@ namespace XNeedle
public SourceLocation WithLineNumber(int lineNumber) => public SourceLocation WithLineNumber(int lineNumber) =>
new SourceLocation(FilePath, Encoding, lineNumber); new SourceLocation(FilePath, Encoding, lineNumber);
public SourceLocation AsSynthetic() =>
new SourceLocation("<synthetic>", Encoding, -1);
} }
} }

View File

@ -35,6 +35,9 @@ namespace XNeedle
static void RunOptions(Options opts) static void RunOptions(Options opts)
{ {
// Auto-discover operations via reflection
BangOperation.RegisterAll();
var masterRule = (Rule)XnParser.RuleObject.Parse(File.ReadAllText(opts.XnDefinition)); var masterRule = (Rule)XnParser.RuleObject.Parse(File.ReadAllText(opts.XnDefinition));
var doc = XDocument.Load(opts.Input, LoadOptions.PreserveWhitespace); var doc = XDocument.Load(opts.Input, LoadOptions.PreserveWhitespace);
var encoding = XmlRoundtripWriter.DetectEncoding(File.ReadAllBytes(opts.Input)); var encoding = XmlRoundtripWriter.DetectEncoding(File.ReadAllBytes(opts.Input));

31
Transformation/Bang.cs Normal file
View File

@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using XNeedle;
using XNeedle.Parser.Elements;
namespace XNeedle.Transformation
{
public class Bang : Part
{
public required string Name { get; set; }
public required IEnumerable<string> Arguments;
public required IEnumerable<Part> Body;
public override void Operate(Context ctx)
{
var bangop = BangOperation.Resolve(Name);
if (bangop == null)
{
throw new ArgumentException($"!{Name} is not a valid bang.");
}
var bodyctx = bangop.Execute(ctx, Arguments);
Console.WriteLine($"Operate Bang: {Name}, {string.Join(",", Arguments)}");
foreach (var b in Body)
{
b.Operate(bodyctx);
}
}
}
}

View File

@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using XNeedle.Parser.Elements;
namespace XNeedle.Transformation
{
public abstract class BangOperation
{
private static readonly Dictionary<string, BangOperation> Registry = new();
public abstract string Name { get; }
protected BangOperation()
{
Registry[Name] = this;
}
public abstract Context Execute(Context ctx, IEnumerable<string> arguments);
public static BangOperation? Resolve(string name) =>
Registry.TryGetValue(name, out var op) ? op : null;
public static void RegisterAll()
{
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
if (!type.IsAbstract && type.IsSubclassOf(typeof(BangOperation)))
Activator.CreateInstance(type);
}
}
}

View File

@ -0,0 +1,28 @@
using System.Xml.Linq;
using XNeedle.Parser.Elements;
namespace XNeedle.Transformation.Bangs
{
public class AddBang : BangOperation
{
public override string Name => "add";
public override Context Execute(Context ctx, IEnumerable<string> arguments)
{
if (ctx.Node == null)
{
throw new ArgumentNullException("!add needs a reachable node");
}
if (arguments.Count() < 1)
{
throw new ArgumentException("!add needs at least the tag name as argument.");
}
var tagname = arguments.ElementAt(0);
var element = new XElement(tagname, "");
ctx.Node.Add(element);
return new Context{ Node = element, SLoc = ctx.SLoc.AsSynthetic(), XPathSelector = tagname};
}
}
}

View File

@ -5,6 +5,8 @@ using Sprache;
using XNeedle.Formats; using XNeedle.Formats;
using XNeedle.Parser; using XNeedle.Parser;
using XNeedle.Parser.Elements; using XNeedle.Parser.Elements;
using System.Xml.Linq;
using System.Xml.XPath;
namespace XNeedle.Transformation namespace XNeedle.Transformation
{ {
@ -19,7 +21,7 @@ namespace XNeedle.Transformation
public static double GetLevel(Context ctx) public static double GetLevel(Context ctx)
{ {
int level = 0; int level = 0;
System.Xml.Linq.XElement? n = ctx.Node; XElement? n = ctx.Node;
while (null != (n = n?.Parent)) while (null != (n = n?.Parent))
level++; level++;
return level; return level;
@ -106,5 +108,28 @@ namespace XNeedle.Transformation
} }
return ""; return "";
} }
public static string swap(Context ctx, string a_select, string b_select)
{
if (ctx.Node == null)
{
throw new ArgumentNullException("!seap needs a reachable node");
}
var a_node = ctx.Node.XPathSelectElement(a_select);
var b_node = ctx.Node.XPathSelectElement(b_select);
if (a_node == null || b_node == null)
{
throw new InvalidOperationException($"!swap could not find nodes for \"{a_select}\" and/or \"{b_select}\".");
}
var placeholder = new XElement("__placeholder__");
a_node.ReplaceWith(placeholder);
b_node.ReplaceWith(a_node);
placeholder.ReplaceWith(b_node);
return "";
}
} }
} }