private bool TryReadFrac( out FloatLiteral frac, int fracBase, Func <BigInteger, char, BigInteger?> tryAccumulateFracDigit) { (BigInteger, int)pair; bool parsed = TryReadNum(out pair, (BigInteger.Zero, 0), (acc, c) => { var(i, n) = acc; var maybeAcc = tryAccumulateFracDigit(i, c); if (maybeAcc.HasValue) { return(maybeAcc.Value, n + 1); } else { return(null); } }); if (parsed) { var(i, n) = pair; frac = FloatLiteral.Number(i, fracBase, -n); return(true); } else { frac = FloatLiteral.Zero(fracBase); return(false); } }
private bool TryAppendExponent( FloatLiteral floatNum, out FloatLiteral result) { bool negateExp = false; if (Expect('-')) { negateExp = true; } else { Expect('+'); } BigInteger exp; if (!TryReadNum(out exp)) { result = floatNum; return(false); } else { result = floatNum.AddToExponent(MaybeNegate(exp, negateExp)); return(true); } }
/// <summary> /// Losslessly changes a float literal's base. Base changes only work if old base /// is a power of the new base. /// </summary> /// <param name="newBase">The new base.</param> /// <returns>An equivalent float literal with base <paramref name="newBase"/>.</returns> public FloatLiteral ChangeBase(int newBase) { if (Kind != FloatLiteralKind.Number || Base == newBase) { return(this); } else if (Exponent == 0) { return(FloatLiteral.Number(IsNegative, Significand, newBase, 0)); } // Note: `x * (n ^ m) ^ k` equals `x * n ^ (m * k)`. var power = 1; var resultBase = Base; while (resultBase != newBase) { if (resultBase < newBase || resultBase % newBase != 0) { throw new InvalidOperationException( $"Float literal '{this}' with base '{Base}' cannot be transformed losslessly to float with base '{newBase}'."); } resultBase /= newBase; power++; } return(FloatLiteral.Number(IsNegative, Significand, newBase, power * Exponent)); }
private Token ReadKeywordToken() { var spanStart = offset; char c; if (!TryPeekChar(out c) || c < 'a' || c > 'z') { return(ReadReservedToken(spanStart)); } var builder = new StringBuilder(); while (TryReadIdentifierChar(out c)) { builder.Append(c); } var span = new SourceSpan(document, spanStart, offset - spanStart); var result = builder.ToString(); // Some floating point tokens look like keywords, so we'll handle // them here as well as in the FP parsing routine. if (result == "nan") { return(new Token(TokenKind.Float, span, FloatLiteral.NaN(false))); } else if (result.StartsWith("nan:0x", StringComparison.Ordinal)) { var payload = result.Substring("nan:0x".Length); long newBits = 0; for (int i = 0; i < payload.Length; i++) { int digit; if (payload[i] == '_') { continue; } else if (TryParseHexDigit(payload[i], out digit)) { newBits = newBits * 16 + digit; } else { return(new Token(TokenKind.Keyword, span, result)); } } return(new Token(TokenKind.Float, span, FloatLiteral.NaN(false, newBits))); } else if (result == "inf") { return(new Token(TokenKind.Float, span, FloatLiteral.PositiveInfinity)); } else { return(new Token(TokenKind.Keyword, span, result)); } }
private bool TryReadUnsignedNumber( bool negate, out object result, IntegerReader tryReadNum, FloatReader tryReadFrac, char exponentChar, int exponent) { BigInteger num; if (tryReadNum(out num)) { if (Expect('.')) { FloatLiteral frac; if (!tryReadFrac(out frac)) { frac = FloatLiteral.Zero(exponent); } var floatNum = num + frac; if (Expect(exponentChar) || Expect(char.ToUpperInvariant(exponentChar))) { floatNum = floatNum.ChangeBase(exponent); if (!TryAppendExponent(floatNum, out floatNum)) { result = null; return(false); } } result = MaybeNegate(floatNum, negate); } else if (Expect(exponentChar) || Expect(char.ToUpperInvariant(exponentChar))) { FloatLiteral floatNum; if (!TryAppendExponent(FloatLiteral.Number(num, exponent), out floatNum)) { result = null; return(false); } result = MaybeNegate(floatNum, negate); } else { result = MaybeNegate(num, negate); } return(true); } else { result = null; return(false); } }
private bool TryReadFrac(out FloatLiteral frac) { return(TryReadFrac(out frac, 10, (i, c) => { if (c >= '0' && c <= '9') { return i * 10 + (c - '0'); } else { return null; } })); }
private bool TryReadHexFrac(out FloatLiteral frac) { return(TryReadFrac(out frac, 16, (i, c) => { int digit; if (TryParseHexDigit(c, out digit)) { return i * 16 + digit; } else { return null; } })); }
private bool TryReadUnsignedNumber(bool negate, out object result) { if (Expect("nan:0x")) { BigInteger hexNum; if (TryReadHexNum(out hexNum)) { result = FloatLiteral.NaN(negate, (long)hexNum); return(true); } else { result = FloatLiteral.NaN(negate); return(false); } } else if (Expect("nan")) { result = FloatLiteral.NaN(negate); return(true); } else if (Expect("inf")) { result = MaybeNegate(FloatLiteral.PositiveInfinity, negate); return(true); } else if (Expect("0x")) { return(TryReadUnsignedNumber( negate, out result, TryReadHexNum, TryReadHexFrac, 'p', 2)); } else { return(TryReadUnsignedNumber( negate, out result, TryReadNum, TryReadFrac, 'e', 10)); } }
private FloatLiteral MaybeNegate(FloatLiteral v, bool negate) { return(negate ? -v : v); }
/// <summary> /// Runs a single expression in the script. /// </summary> /// <param name="expression">The expression to run.</param> public TestStatistics Run(SExpression expression) { if (expression.IsCallTo("module")) { var module = Assembler.AssembleModule(expression, out string moduleId); var instance = Wasm.Interpret.ModuleInstance.Instantiate( module, importer, policy: ExecutionPolicy.Create(maxMemorySize: 0x1000), compiler: Compiler); moduleInstances.Add(instance); if (moduleId != null) { moduleInstancesByName[moduleId] = instance; } if (module.StartFunctionIndex.HasValue) { instance.Functions[(int)module.StartFunctionIndex.Value].Invoke(Array.Empty <object>()); } return(TestStatistics.Empty); } else if (expression.IsCallTo("register")) { var tail = expression.Tail; var name = Assembler.AssembleString(tail[0], Log); tail = tail.Skip(1).ToArray(); var moduleId = Assembler.AssembleLabelOrNull(ref tail); if (moduleId == null) { importer.RegisterImporter(name, new ModuleExportsImporter(moduleInstances[moduleInstances.Count - 1])); } else { importer.RegisterImporter(name, new ModuleExportsImporter(moduleInstancesByName[moduleId])); } return(TestStatistics.Empty); } else if (expression.IsCallTo("invoke") || expression.IsCallTo("get")) { RunAction(expression); return(TestStatistics.SingleSuccess); } else if (expression.IsCallTo("assert_return")) { var results = RunAction(expression.Tail[0]); var expected = expression.Tail .Skip(1) .Zip(results, (expr, val) => EvaluateConstExpr(expr, val.GetType())) .ToArray(); if (expected.Length != results.Count) { Log.Log( new LogEntry( Severity.Error, "assertion failed", "action produced result ", string.Join(", ", results), "; expected ", string.Join(", ", expected), ".", Assembler.Highlight(expression))); return(TestStatistics.SingleFailure); } bool failures = false; for (int i = 0; i < expected.Length; i++) { if (!object.Equals(results[i], expected[i])) { if (AlmostEquals(results[i], expected[i])) { Log.Log( new LogEntry( Severity.Warning, "rounding error", "action produced result ", results[i].ToString(), "; expected ", expected[i].ToString(), ".", Assembler.Highlight(expression))); } else { Log.Log( new LogEntry( Severity.Error, "assertion failed", "action produced result ", results[i].ToString(), "; expected ", expected[i].ToString(), ".", Assembler.Highlight(expression))); failures = true; } } } return(failures ? TestStatistics.SingleFailure : TestStatistics.SingleSuccess); } else if (expression.IsCallTo("assert_trap") || expression.IsCallTo("assert_exhaustion")) { var expected = Assembler.AssembleString(expression.Tail[1], Log); bool caught = false; Exception exception = null; try { if (expression.Tail[0].IsCallTo("module")) { Run(expression.Tail[0]); } else { RunAction(expression.Tail[0], false); } } catch (TrapException ex) { caught = ex.SpecMessage == expected; exception = ex; } catch (PixieException) { throw; } catch (Exception ex) { caught = false; exception = ex; } if (caught) { return(TestStatistics.SingleSuccess); } else { if (exception == null) { Log.Log( new LogEntry( Severity.Error, "assertion failed", "action should have trapped, but didn't.", Assembler.Highlight(expression))); } else { Log.Log( new LogEntry( Severity.Error, "assertion failed", "action trapped as expected, but with an unexpected exception. ", exception.ToString(), Assembler.Highlight(expression))); } return(TestStatistics.SingleFailure); } } else if (expression.IsCallTo("assert_return_canonical_nan")) { var results = RunAction(expression.Tail[0]); bool isCanonicalNaN; if (results.Count != 1) { Log.Log( new LogEntry( Severity.Error, "assertion failed", "action produced ", results.Count.ToString(), " results (", string.Join(", ", results), "); expected a single canonical NaN.", Assembler.Highlight(expression))); return(TestStatistics.SingleFailure); } else if (results[0] is double) { var val = Interpret.ValueHelpers.ReinterpretAsInt64((double)results[0]); isCanonicalNaN = val == Interpret.ValueHelpers.ReinterpretAsInt64((double)FloatLiteral.NaN(false)) || val == Interpret.ValueHelpers.ReinterpretAsInt64((double)FloatLiteral.NaN(true)); } else if (results[0] is float) { var val = Interpret.ValueHelpers.ReinterpretAsInt32((float)results[0]); isCanonicalNaN = val == Interpret.ValueHelpers.ReinterpretAsInt32((float)FloatLiteral.NaN(false)) || val == Interpret.ValueHelpers.ReinterpretAsInt32((float)FloatLiteral.NaN(true)); } else { isCanonicalNaN = false; } if (isCanonicalNaN) { return(TestStatistics.SingleSuccess); } else { Log.Log( new LogEntry( Severity.Error, "assertion failed", "action produced ", results[0].ToString(), "; expected a single canonical NaN.", Assembler.Highlight(expression))); return(TestStatistics.SingleFailure); } } else if (expression.IsCallTo("assert_return_arithmetic_nan")) { var results = RunAction(expression.Tail[0]); bool isNaN; if (results.Count != 1) { Log.Log( new LogEntry( Severity.Error, "assertion failed", "action produced ", results.Count.ToString(), " results (", string.Join(", ", results), "); expected a single NaN.", Assembler.Highlight(expression))); return(TestStatistics.SingleFailure); } else if (results[0] is double) { isNaN = double.IsNaN((double)results[0]); } else if (results[0] is float) { isNaN = float.IsNaN((float)results[0]); } else { isNaN = false; } if (isNaN) { return(TestStatistics.SingleSuccess); } else { Log.Log( new LogEntry( Severity.Error, "assertion failed", "action produced ", results[0].ToString(), "; expected a single NaN.", Assembler.Highlight(expression))); return(TestStatistics.SingleFailure); } } else { Log.Log( new LogEntry( Severity.Warning, "unknown script command", Quotation.QuoteEvenInBold( "expression ", expression.Head.Span.Text, " was not recognized as a known script command."), Assembler.Highlight(expression))); return(TestStatistics.SingleUnknown); } }