From 4629002ef09190b58ded4b0275918d7ddc7b1fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominic=20H=C3=B6glinger?= Date: Sun, 26 Apr 2026 17:42:51 +0000 Subject: [PATCH] Enhance parser and add a few functions, fix hex parsing (#4) 1. Unify all numbers under double type A bit lazy, but is easily maintainable. Precision is enough for most use cases. 2. Add more functions Add `num`, `parselit`, `str`. 3. Fix hex parser error Previously the "0x" was greedy and matches single zeros, which then raised a parsing error because it couldn't match a hex number. Reviewed-on: https://git.hoeglinger.eu/dominic/xn/pulls/4 --- src/Parser/XnParser.cs | 15 +++++--- src/Transformation/ContextFunctions.cs | 49 ++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/Parser/XnParser.cs b/src/Parser/XnParser.cs index 18e58c3..1e7c54e 100644 --- a/src/Parser/XnParser.cs +++ b/src/Parser/XnParser.cs @@ -78,15 +78,22 @@ namespace XNeedle.Parser string.Join(",", parameters.Select(e => e.Type.Name)))); } + // This needs to match as one atomic token, else with a separate String("0x") the 0 is consumed, + // which triggers a parsing error on single zero numbers public static readonly Parser HexNumber = - from prefix in Parse.String("0x").Token() - from number in Parse.Char(char.IsAsciiHexDigit, "hexadecimal digit").AtLeastOnce().Text() - select number; + Parse.Regex(@"0x[0-9a-fA-F]+").Token() + .Select(s => s.Substring(2)); + + public static readonly Parser BooleanLiteral = + from value in Parse.String("true").XOr(Parse.String("false")).Text() + select value; static readonly Parser NumberConstant = - HexNumber.Select(x => Expression.Constant(int.Parse(x, System.Globalization.NumberStyles.HexNumber))) + HexNumber.Select(x => Expression.Constant((double)int.Parse(x, System.Globalization.NumberStyles.HexNumber))) + .XOr(BooleanLiteral.Select(x => Expression.Constant(x == "true" ? 1.0 : 0.0))) .XOr(Parse.Decimal.Select(x => Expression.Constant(double.Parse(x)))) .Named("number"); + private static readonly Parser StringConstant = from open in Parse.Char('"') diff --git a/src/Transformation/ContextFunctions.cs b/src/Transformation/ContextFunctions.cs index 1580970..45e7a9d 100644 --- a/src/Transformation/ContextFunctions.cs +++ b/src/Transformation/ContextFunctions.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Text; using Sprache; @@ -12,12 +13,54 @@ namespace XNeedle.Transformation { public class ContextFunctions { - public static string hexlit(Context ctx, int value) + + public static double num(Context ctx, string value, double numbase = 10) + { + if (string.IsNullOrWhiteSpace(value)) + { + return 0; + } + + string cleanedValue = value.Trim(); + + if (numbase != 10) + { + cleanedValue = cleanedValue.TrimStart('0'); + cleanedValue = cleanedValue.TrimStart('x'); + + // Convert to Int64 to not lose any fidelity + return Convert.ToInt64(cleanedValue, (int)numbase); + } + + return double.Parse(cleanedValue, CultureInfo.InvariantCulture); + } + public static string str(Context ctx, double value, double numbase = 10) + { + if (numbase == 10) + { + return $"{value}"; + } + + // Ignore fraction for other bases by casting to long + return Convert.ToString((long)value, (int)numbase); + } + + public static string hexlit(Context ctx, double value) { uint v = (uint)value; return $"#x{v:x8}"; } + public static double parselit(Context ctx, string literal, double numbase = 16) + { + if (string.IsNullOrWhiteSpace(literal) || literal.Length <= 2) + { + throw new ArgumentException($"Cannot parse literal \"{literal}\""); + } + + return (double)Convert.ToInt32(literal.Substring(2), (int)numbase); + } + public static string lower(Context ctx, string text) { return text.ToLower(); @@ -70,7 +113,7 @@ namespace XNeedle.Transformation return reflected; } - public static int crc32(Context ctx, int poly = 0x04C11DB7, int init = 0, int xorOut = 0, bool reflected = false) + public static double crc32(Context ctx, double poly = 0x04C11DB7, double init = 0, double xorOut = 0, double reflected = 0.0) { if (ctx.Node is null) return 0; @@ -88,7 +131,7 @@ namespace XNeedle.Transformation byte[] fragBytes = Encoding.UTF8.GetBytes(data); // Step 3: Digest into CRC32 using parameterized CRC32 - by default MPEG-2 - return (int)ComputeCrc32(fragBytes, (uint)poly, (uint)init, (uint)xorOut, reflected); + return (double)ComputeCrc32(fragBytes, (uint)poly, (uint)init, (uint)xorOut, reflected == 1.0); } public static string text(Context ctx)