| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:math'; | |
| import '../messages/codes.dart'; | |
| import 'value_kind.dart'; | |
| mixin StackChecker { | |
| /// Used to report an internal error encountered in the stack listener. | |
| Never internalProblem(Message message, int charOffset, Uri uri); | |
| /// Checks that [value] matches the expected [kind]. | |
| /// | |
| /// Use this in assert statements like | |
| /// | |
| /// assert(checkStackValue(uri, fileOffset, ValueKind.Token, value)); | |
| /// | |
| /// to document and validate the expected value kind. | |
| bool checkStackValue( | |
| Uri uri, | |
| int? fileOffset, | |
| ValueKind kind, | |
| Object? value, | |
| ) { | |
| if (!kind.check(value)) { | |
| String message = | |
| 'Unexpected value `${value}` (${value.runtimeType}). ' | |
| 'Expected ${kind}.'; | |
| if (fileOffset != null) { | |
| // If offset is available report and internal problem to show the | |
| // parsed code in the output. | |
| throw internalProblem( | |
| new Message(const Code('Internal error'), problemMessage: message), | |
| fileOffset, | |
| uri, | |
| ); | |
| } else { | |
| throw message; | |
| } | |
| } | |
| return true; | |
| } | |
| /// Returns the size of the stack. | |
| int get stackHeight; | |
| /// Returns the [index]th element on the stack from the top, i.e. the top of | |
| /// the stack has index 0. | |
| Object? lookupStack(int index); | |
| /// Checks that [base] is a valid base stack height for a call to | |
| /// [checkStackStateForAssert]. | |
| /// | |
| /// This can be used to initialize a stack base for subsequent calls to | |
| /// [checkStackStateForAssert]. For instance: | |
| /// | |
| /// int? stackBase; | |
| /// // Set up the current stack height as the stack base. | |
| /// assert(checkStackBaseState( | |
| /// uri, fileOffset, stackBase = stackHeight)); | |
| /// ... | |
| /// // Check that the stack is empty, relative to the stack base. | |
| /// assert(checkStackState( | |
| /// uri, fileOffset, [], base: stackBase)); | |
| /// | |
| /// or | |
| /// | |
| /// int? stackBase; | |
| /// // Assert that the current stack height is at least 4 and set | |
| /// // the stack height - 4 up as the stack base. | |
| /// assert(checkStackBaseState( | |
| /// uri, fileOffset, stackBase = stackHeight - 4)); | |
| /// ... | |
| /// // Check that the stack contains a single `Foo` element, relative to | |
| /// // the stack base. | |
| /// assert(checkStackState( | |
| /// uri, fileOffset, [ValuesKind.Foo], base: stackBase)); | |
| /// | |
| bool checkStackBaseStateForAssert(Uri uri, int fileOffset, int base) { | |
| if (base < 0) { | |
| _throwProblem( | |
| uri, | |
| fileOffset, | |
| "Too few elements on stack. " | |
| "Expected ${stackHeight - base}, found $stackHeight.", | |
| ); | |
| } | |
| return true; | |
| } | |
| /// Checks the top of the current stack against [kinds]. If a mismatch is | |
| /// found, a top of the current stack is print along with the expected [kinds] | |
| /// marking the frames that don't match, and throws an exception. | |
| /// | |
| /// If [base] provided, it is used as the reference stack base height at | |
| /// which the [kinds] are expected to occur. This allows for checking that | |
| /// the stack is empty wrt. the stack base height. | |
| /// | |
| /// Use this in assert statements like | |
| /// | |
| /// assert(checkStackState( | |
| /// uri, fileOffset, [ValueKind.Integer, ValueKind.StringOrNull])) | |
| /// | |
| /// to document the expected stack and get earlier errors on unexpected stack | |
| /// content. | |
| bool checkStackStateForAssert( | |
| Uri uri, | |
| int? fileOffset, | |
| List<ValueKind> kinds, { | |
| int? base, | |
| }) { | |
| String? heightError; | |
| String? kindError; | |
| bool success = true; | |
| int stackShift = 0; | |
| if (base != null) { | |
| int relativeStackHeight = stackHeight - base; | |
| if (relativeStackHeight < kinds.length) { | |
| heightError = | |
| "Too few elements on stack. " | |
| "Expected ${kinds.length}, found $relativeStackHeight."; | |
| success = false; | |
| } else if (relativeStackHeight > kinds.length) { | |
| heightError = | |
| "Too many elements on stack. " | |
| "Expected ${kinds.length}, found $relativeStackHeight."; | |
| success = false; | |
| } | |
| // Shift the stack lookup indices so that [kinds] are checked relative | |
| // to the stack base instead of relative to the top of the stack. | |
| stackShift = relativeStackHeight - kinds.length; | |
| } else { | |
| if (stackHeight < kinds.length) { | |
| heightError = | |
| "Too few elements on stack. " | |
| "Expected ${kinds.length}, found $stackHeight."; | |
| success = false; | |
| } | |
| } | |
| for (int kindIndex = 0; kindIndex < kinds.length; kindIndex++) { | |
| ValueKind kind = kinds[kindIndex]; | |
| int stackOffset = kindIndex + stackShift; | |
| if (0 <= stackOffset && stackOffset < stackHeight) { | |
| Object? value = lookupStack(stackOffset); | |
| if (!kind.check(value)) { | |
| kindError = "Unexpected element kind(s)."; | |
| success = false; | |
| } | |
| } else { | |
| success = false; | |
| } | |
| } | |
| if (!success) { | |
| StringBuffer sb = new StringBuffer(); | |
| if (heightError != null) { | |
| sb.writeln(' $heightError'); | |
| } | |
| if (kindError != null) { | |
| sb.writeln(' $kindError'); | |
| } | |
| String safeToString(Object? object) { | |
| try { | |
| return '$object'.replaceAll('\r', '').replaceAll('\n', ''); | |
| } catch (e) { | |
| // Judgments fail on toString. | |
| return object.runtimeType.toString(); | |
| } | |
| } | |
| String padLeft(Object object, int length) { | |
| String text = safeToString(object); | |
| if (text.length < length) { | |
| return ' ' * (length - text.length) + text; | |
| } | |
| return text; | |
| } | |
| String padRight(Object object, int length) { | |
| String text = safeToString(object); | |
| if (text.length < length) { | |
| return text + ' ' * (length - text.length); | |
| } | |
| return text; | |
| } | |
| // Compute kind/stack frame information for all expected values plus 3 more | |
| // stack elements if available. | |
| int startIndex = min(-stackShift, 0); | |
| int endIndex = max(kinds.length + stackShift, stackHeight); | |
| for (int kindIndex = startIndex; kindIndex < endIndex + 3; kindIndex++) { | |
| int stackOffset = kindIndex + stackShift; | |
| if (stackOffset >= stackHeight && kindIndex > kinds.length) { | |
| // No more stack elements nor kinds to display. | |
| break; | |
| } | |
| if (kindIndex == kinds.length && base != null) { | |
| // Show where the stack base is in the stack. Elements printed above | |
| // this line are the checked/expected stack. | |
| sb.write('>'); | |
| } else { | |
| sb.write(' '); | |
| } | |
| if (stackOffset >= 0) { | |
| sb.write(padLeft(stackOffset, 3)); | |
| } else { | |
| sb.write(padLeft('*', 3)); | |
| } | |
| sb.write(': '); | |
| ValueKind? kind; | |
| if (kindIndex < 0) { | |
| sb.write(padRight('', 60)); | |
| } else if (kindIndex < kinds.length) { | |
| kind = kinds[kindIndex]; | |
| sb.write(padRight(kind, 60)); | |
| } else { | |
| sb.write(padRight('---', 60)); | |
| } | |
| if (0 <= stackOffset && stackOffset < stackHeight) { | |
| Object? value = lookupStack(stackOffset); | |
| if (kind == null || kind.check(value)) { | |
| sb.write(' '); | |
| } else { | |
| sb.write('*'); | |
| } | |
| sb.write(safeToString(value)); | |
| sb.write(' (${value.runtimeType})'); | |
| } else { | |
| if (kind == null) { | |
| sb.write(' '); | |
| } else { | |
| sb.write('*'); | |
| } | |
| sb.write('---'); | |
| } | |
| sb.writeln(); | |
| } | |
| _throwProblem(uri, fileOffset, sb.toString()); | |
| } | |
| return success; | |
| } | |
| Never _throwProblem(Uri uri, int? fileOffset, String text) { | |
| String message = '$runtimeType failure\n$text'; | |
| if (fileOffset != null) { | |
| // If offset is available report and internal problem to show the | |
| // parsed code in the output. | |
| throw internalProblem( | |
| new Message(const Code('Internal error'), problemMessage: message), | |
| fileOffset, | |
| uri, | |
| ); | |
| } else { | |
| throw message; | |
| } | |
| } | |
| } |