Skip to content

Commit

Permalink
v0.6.4
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanblake4 committed Oct 10, 2023
1 parent 3c869be commit 4f6b69f
Show file tree
Hide file tree
Showing 19 changed files with 301 additions and 9 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.6.4
- Support for casting (`as`)
- Support for asserts and `AssertionError`
- Add magic constant and version to EVC bytecode to prevent
errors.
- Fix for error when extending a bridge class

## 0.6.3
- Support for cascades
- Support for null coalescing assignment operator `??=`
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,8 @@ may vary when bridging.
| Typedefs || N/A |
| Generic classes | Partial ||
| Type tests (`is`) || [[1]](https://github.com/ethanblake4/dart_eval/blob/master/test/expression_test.dart#L12), [[2]](https://github.com/ethanblake4/dart_eval/blob/master/test/expression_test.dart#L44) |
| Casting (`as`) | | N/A |
| `assert` | | N/A |
| Casting (`as`) | | [[1]](https://github.com/ethanblake4/dart_eval/blob/master/test/exception_test.dart#L206), [[2]](https://github.com/ethanblake4/dart_eval/blob/master/test/exception_test.dart#L227), [[3]](https://github.com/ethanblake4/dart_eval/blob/master/test/exception_test.dart#L244) |
| `assert` | | [[1]](https://github.com/ethanblake4/dart_eval/blob/master/test/exception_test.dart#L115) |
| Null safety | Partial ||
| Late initialization || N/A |
| Cascades || [[1]](https://github.com/ethanblake4/dart_eval/blob/master/test/expression_test.dart#L136), [[2]](https://github.com/ethanblake4/dart_eval/blob/master/test/expression_test.dart#L160) |
Expand All @@ -460,6 +460,8 @@ may vary when bridging.
| Extension methods || N/A |
| Const expressions | Partial | N/A |
| Isolates || N/A |
| Record types || N/A |
| Patterns || N/A |

## Features and bugs

Expand Down
38 changes: 38 additions & 0 deletions lib/src/eval/compiler/expression/as.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/source_node_wrapper.dart';
import 'package:dart_eval/src/eval/compiler/context.dart';
import 'package:dart_eval/src/eval/compiler/expression/expression.dart';
import 'package:dart_eval/src/eval/compiler/type.dart';
import 'package:dart_eval/src/eval/compiler/variable.dart';
import 'package:dart_eval/src/eval/runtime/runtime.dart';
import 'package:dart_eval/src/eval/shared/types.dart';

Variable compileAsExpression(AsExpression e, CompilerContext ctx) {
var V = compileExpression(e.expression, ctx);
final slot = TypeRef.fromAnnotation(ctx, ctx.library, e.type);

/// If the type is the slot, we can just return
if (V.type == slot) {
return V;
}

// Otherwise type-test
ctx.pushOp(IsType.make(V.scopeFrameOffset, runtimeTypeMap[slot] ?? ctx.typeRefIndexMap[slot]!, false), IsType.LEN);
final Vis = Variable.alloc(ctx, EvalTypes.boolType.copyWith(boxed: false));

// And assert
final errMsg = BuiltinValue(stringval: "TypeError: Not a subtype of type TYPE").push(ctx);
ctx.pushOp(Assert.make(Vis.scopeFrameOffset, errMsg.scopeFrameOffset), Assert.LEN);

// If the type changes between num and int/double, unbox/box
if (slot == EvalTypes.numType) {
V = V.boxIfNeeded(ctx);
} else if (slot == EvalTypes.intType || slot == EvalTypes.doubleType) {
V = V.unboxIfNeeded(ctx);
}

// For all other types, just inform the compiler
// TODO: mixins may need different behavior
return V.copyWithUpdate(ctx, type: slot.copyWith(boxed: V.type.boxed));
}
3 changes: 3 additions & 0 deletions lib/src/eval/compiler/expression/expression.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:dart_eval/src/eval/compiler/context.dart';
import 'package:dart_eval/src/eval/compiler/errors.dart';
import 'package:dart_eval/src/eval/compiler/expression/as.dart';
import 'package:dart_eval/src/eval/compiler/expression/assignment.dart';
import 'package:dart_eval/src/eval/compiler/expression/await.dart';
import 'package:dart_eval/src/eval/compiler/expression/binary.dart';
Expand Down Expand Up @@ -65,6 +66,8 @@ Variable compileExpression(Expression e, CompilerContext ctx, [TypeRef? bound])
return compileIsExpression(e, ctx);
} else if (e is CascadeExpression) {
return compileCascadeExpression(e, ctx);
} else if (e is AsExpression) {
return compileAsExpression(e, ctx);
}

throw CompileError('Unknown expression type ${e.runtimeType}');
Expand Down
3 changes: 1 addition & 2 deletions lib/src/eval/compiler/expression/method_invocation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,7 @@ DeclarationOrBridge<MethodDeclaration, BridgeMethodDef> resolveInstanceMethod(
if ($class.extendsClause == null) {
throw CompileError('Cannot resolve instance method', source);
}
// ignore: deprecated_member_use
final $supertype = ctx.visibleTypes[instanceType.file]![$class.extendsClause!.superclass.name2.stringValue]!;
final $supertype = ctx.visibleTypes[instanceType.file]![$class.extendsClause!.superclass.name2.value()]!;
return resolveInstanceMethod(ctx, $supertype, methodName, source);
}
}
Expand Down
3 changes: 3 additions & 0 deletions lib/src/eval/compiler/program.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ class Program {
Uint8List write() {
final b = BytesBuilder(copy: false);

b.add([0x45, 0x56, 0x43, 0x00]); // EVC\0
b.add(Evc.i32b(64)); // version

_writeMetaBlock(b, topLevelDeclarations.map((key, value) => MapEntry(key.toString(), value)));
_writeMetaBlock(b, instanceDeclarations.map((key, value) => MapEntry(key.toString(), value)));
//_writeMetaBlock(b, typeNames);
Expand Down
24 changes: 24 additions & 0 deletions lib/src/eval/compiler/statement/assert.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/src/eval/compiler/builtins.dart';
import 'package:dart_eval/src/eval/compiler/context.dart';
import 'package:dart_eval/src/eval/compiler/expression/expression.dart';
import 'package:dart_eval/src/eval/compiler/statement/statement.dart';
import 'package:dart_eval/src/eval/compiler/type.dart';
import 'package:dart_eval/src/eval/compiler/variable.dart';
import 'package:dart_eval/src/eval/runtime/runtime.dart';

StatementInfo compileAssertStatement(AssertStatement s, CompilerContext ctx, AlwaysReturnType? expectedReturnType) {
final cond = compileExpression(s.condition, ctx);
final msg = s.message != null ? compileExpression(s.message!, ctx) : BuiltinValue().push(ctx);

msg.pushArg(ctx);
ctx.pushOp(InvokeExternal.make(ctx.bridgeStaticFunctionIndices[ctx.libraryMap['dart:core']]!['AssertionError.']!),
InvokeExternal.LEN);
ctx.pushOp(PushReturnValue.make(), PushReturnValue.LEN);
final assertionErr = Variable.alloc(ctx, TypeRef.fromBridgeTypeRef(ctx, BridgeTypeRef(CoreTypes.assertionError)));

ctx.pushOp(Assert.make(cond.scopeFrameOffset, assertionErr.scopeFrameOffset), Assert.LEN);

return StatementInfo(-1);
}
3 changes: 3 additions & 0 deletions lib/src/eval/compiler/statement/statement.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/src/eval/compiler/context.dart';
import 'package:dart_eval/src/eval/compiler/errors.dart';
import 'package:dart_eval/src/eval/compiler/expression/expression.dart';
import 'package:dart_eval/src/eval/compiler/statement/assert.dart';
import 'package:dart_eval/src/eval/compiler/statement/do.dart';
import 'package:dart_eval/src/eval/compiler/statement/for.dart';
import 'package:dart_eval/src/eval/compiler/statement/if.dart';
Expand Down Expand Up @@ -37,6 +38,8 @@ StatementInfo compileStatement(Statement s, AlwaysReturnType? expectedReturnType
return compileIfStatement(s, ctx, expectedReturnType);
} else if (s is TryStatement) {
return compileTryStatement(s, ctx, expectedReturnType);
} else if (s is AssertStatement) {
return compileAssertStatement(s, ctx, expectedReturnType);
} else {
throw CompileError('Unknown statement type ${s.runtimeType}');
}
Expand Down
5 changes: 4 additions & 1 deletion lib/src/eval/compiler/statement/variable_declaration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ void compileVariableDeclarationList(VariableDeclarationList l, CompilerContext c
}
final init = li.initializer;
if (init != null) {
final res = compileExpression(init, ctx, type);
var res = compileExpression(init, ctx, type);
if (type != null && !res.type.resolveTypeChain(ctx).isAssignableTo(ctx, type)) {
throw CompileError(
'Type mismatch: variable "${li.name.value() as String}" is specified as type $type, but is initialized '
'to an incompatible value of type ${res.type}');
}
if (!(type?.isUnboxedAcrossFunctionBoundaries ?? true)) {
res = res.boxIfNeeded(ctx);
}
if (res.name != null) {
var _v = Variable.alloc(ctx, type ?? res.type);
ctx.pushOp(PushNull.make(), PushNull.LEN);
Expand Down
4 changes: 4 additions & 0 deletions lib/src/eval/runtime/ops/all_ops.dart
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ class Evc {
/// [CheckNotEq]
static const OP_CHECK_NOT_EQ = 62;

/// [Assert]
static const OP_ASSERT = 63;

static List<int> i16b(int i16) {
final x = ByteData(2);
x.setInt16(0, i16);
Expand Down Expand Up @@ -301,4 +304,5 @@ final List<OpLoader> ops = [
(Runtime rt) => PopCatch(rt), // 60
(Runtime rt) => IsType(rt), // 61
(Runtime rt) => CheckNotEq(rt), // 62
(Runtime rt) => Assert(rt), // 63
];
23 changes: 23 additions & 0 deletions lib/src/eval/runtime/ops/flow.dart
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,26 @@ class PopCatch implements EvcOp {
@override
String toString() => 'PopCatch()';
}

class Assert implements EvcOp {
Assert(Runtime exec)
: _valueOffset = exec._readInt16(),
_exceptionOffset = exec._readInt16();

Assert.make(this._valueOffset, this._exceptionOffset);

static const int LEN = Evc.BASE_OPLEN + Evc.I16_LEN * 2;

final int _valueOffset;
final int _exceptionOffset;

@override
void run(Runtime runtime) {
if (!(runtime.frame[_valueOffset] as bool)) {
runtime.$throw(runtime.frame[_exceptionOffset]);
}
}

@override
String toString() => 'Assert (!L$_valueOffset, L$_exceptionOffset)';
}
28 changes: 26 additions & 2 deletions lib/src/eval/runtime/ops/objects.dart
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,8 @@ class IsType implements EvcOp {
final type = value.$getRuntimeType(runtime);
if (type < 0) {
final bool result;
if (_type == -2) {
//dynamic
if (_type == -2 || (_type == -4 && type != -3 /* null */)) {
//dynamic and Object
result = true;
} else if (_type == -6) {
// num
Expand All @@ -375,6 +375,30 @@ class IsType implements EvcOp {
String toString() => 'IsType (L$_objectOffset is${_not ? '!' : ''} $_type)';
}

/*/// Cast is a no-op unless the value being cast is a bridge class
class Cast implements EvcOp {
Cast(Runtime runtime)
: _value = runtime._readInt16(),
_type = runtime._readInt32();
final int _value;
final int _type;
Cast.make(this._value, this._type);
static int LEN = Evc.BASE_OPLEN + Evc.I16_LEN + Evc.I32_LEN;
@override
void run(Runtime runtime) {
final value = runtime.frame[_value];
if (value is $Bridge) {
final data = Runtime.bridgeData[value];
}
}
}*/

class CheckNotEq implements EvcOp {
CheckNotEq(Runtime runtime)
: _value1 = runtime._readInt16(),
Expand Down
17 changes: 16 additions & 1 deletion lib/src/eval/runtime/runtime.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ class Runtime {
}

void _load() {
final m1 = _readUint8(), m2 = _readUint8(), m3 = _readUint8(), m4 = _readUint8();
final version = _readInt32();
if (m1 != 0x45 || m2 != 0x56 || m3 != 0x43 || m4 != 0x00) {
throw Exception('dart_eval runtime error: Not an EVC file or bytecode version older than 064');
}
if (version != 64) {
var vstr = version.toString();
if (vstr.length < 3) {
vstr = '0$vstr';
}
throw Exception('dart_eval runtime error: EVC bytecode is version $vstr, but runtime supports version 064.\n'
'Try using the same version of dart_eval for compiling as the version in your application.');
}
final encodedToplevelDecs = _readString();
final encodedInstanceDecs = _readString();
//final encodedTypeNames = _readString();
Expand Down Expand Up @@ -161,7 +174,6 @@ class Runtime {
declaredClasses[file] = {for (final decl in $class.entries) decl.key: EvalClass.fromJson(decl.value)};
});

//typeNames = (json.decode(encodedTypeNames) as List).cast();
typeTypes = [for (final s in (json.decode(encodedTypeTypes) as List)) (s as List).cast<int>().toSet()];

typeIds = (json.decode(encodedTypeIds) as Map)
Expand Down Expand Up @@ -535,6 +547,9 @@ class Runtime {
case IsType:
op as IsType;
return [Evc.OP_IS_TYPE, ...Evc.i16b(op._objectOffset), ...Evc.i32b(op._type), op._not ? 1 : 0];
case Assert:
op as Assert;
return [Evc.OP_ASSERT, ...Evc.i16b(op._valueOffset), ...Evc.i16b(op._exceptionOffset)];
default:
throw ArgumentError('Not a valid op $op');
}
Expand Down
3 changes: 3 additions & 0 deletions lib/src/eval/shared/stdlib/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/src/eval/shared/stdlib/core/collection.dart';
import 'package:dart_eval/src/eval/shared/stdlib/core/date_time.dart';
import 'package:dart_eval/src/eval/shared/stdlib/core/errors.dart';
import 'package:dart_eval/src/eval/shared/stdlib/core/iterator.dart';
import 'package:dart_eval/src/eval/shared/stdlib/core/pattern.dart';
import 'package:dart_eval/src/eval/shared/stdlib/core/regexp.dart';
Expand All @@ -28,6 +29,7 @@ class DartCorePlugin implements EvalPlugin {
registry.defineBridgeClass($Pattern.$declaration);
registry.defineBridgeClass($Match.$declaration);
registry.defineBridgeClass($RegExp.$declaration);
registry.defineBridgeClass($AssertionError.$declaration);
}

@override
Expand All @@ -39,5 +41,6 @@ class DartCorePlugin implements EvalPlugin {
$DateTime.configureForRuntime(runtime);
$Uri.configureForRuntime(runtime);
runtime.registerBridgeFunc('dart:core', 'RegExp.', $RegExp.$new);
runtime.registerBridgeFunc('dart:core', 'AssertionError.', $AssertionError.$new);
}
}
64 changes: 64 additions & 0 deletions lib/src/eval/shared/stdlib/core/errors.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/src/eval/shared/stdlib/core/base.dart';

/// dart_eval wrapper for [AssertionError]
class $AssertionError implements AssertionError, $Instance {
/// Compile-time class definition for [$Iterable]
static const $declaration = BridgeClassDef(BridgeClassType(BridgeTypeRef(CoreTypes.assertionError)),
constructors: {
'': BridgeConstructorDef(BridgeFunctionDef(
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.assertionError)),
params: [
BridgeParameter(
'message', BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.objectType), nullable: true), true)
]))
},
methods: {},
getters: {
'message': BridgeMethodDef(
BridgeFunctionDef(
params: [], returns: BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.objectType), nullable: true)),
isStatic: false),
},
setters: {},
fields: {},
wrap: true);

final $Instance _superclass;

/// Wrap a [AssertionError] in a [$AssertionError]
$AssertionError.wrap(this.$value) : _superclass = $Object($value);

static $AssertionError $new(Runtime runtime, $Value? target, List<$Value?> args) {
return $AssertionError.wrap(AssertionError(args[0]?.$value));
}

@override
final AssertionError $value;

@override
AssertionError get $reified => $value;

@override
int $getRuntimeType(Runtime runtime) => runtime.lookupType(CoreTypes.assertionError);

@override
$Value? $getProperty(Runtime runtime, String identifier) {
if (identifier == 'message') {
return $value.message == null ? $null() : $Object($value.message!);
}
return _superclass.$getProperty(runtime, identifier);
}

@override
void $setProperty(Runtime runtime, String identifier, $Value value) {
return _superclass.$setProperty(runtime, identifier, value);
}

@override
Object? get message => $value.message;

@override
StackTrace? get stackTrace => $value.stackTrace;
}
6 changes: 6 additions & 0 deletions lib/src/eval/shared/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ class CoreTypes {

/// Bridge type spec for [$RegExp]
static const regExp = BridgeTypeSpec('dart:core', 'RegExp');

/// Bridge type spec for [Error]
static const error = BridgeTypeSpec('dart:core', 'Error');

/// Bridge type spec for [AssertionError]
static const assertionError = BridgeTypeSpec('dart:core', 'AssertionError');
}

/// This class contains dart:async bridge type specs for convenience
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: dart_eval
description: A flexible Dart bytecode compiler and interpreter written in Dart, enabling dynamic execution and code push for AOT Dart apps.
version: 0.6.3
version: 0.6.4
homepage: https://github.com/ethanblake4/dart_eval
platforms:
android:
Expand Down
Loading

0 comments on commit 4f6b69f

Please sign in to comment.