diff --git a/pkg/dartino_compiler/lib/cli_debugger.dart b/pkg/dartino_compiler/lib/cli_debugger.dart index cad8a182..f408b7a5 100644 --- a/pkg/dartino_compiler/lib/cli_debugger.dart +++ b/pkg/dartino_compiler/lib/cli_debugger.dart @@ -25,6 +25,7 @@ import "vm_context.dart"; import 'vm_commands.dart' show Array, + ArrayStructure, ClassValue, ConnectionError, DartValue, @@ -84,6 +85,8 @@ Commands: 'q/quit' quit the session """; +const int arrayItemsShownByDefault = 20; + class CommandLineDebugger { final DartinoVmContext vmContext; final Stream stream; @@ -711,6 +714,10 @@ Future _processVariable( List fieldIndices = new List(); List accessesTillHere = [localAccess]; + int startIndex = 0; + // By default request no more than [arrayItemsShownByDefault] items from the + // array. + int endIndex = arrayItemsShownByDefault; for (Access access in accesses.skip(1)) { RemoteValue remoteValue = await vmContext.processLocal( frame, @@ -720,12 +727,7 @@ Future _processVariable( if (value is Instance) { ClassElement classElement = vmContext.dartinoSystem.classesById[value.classId].element; - if (access is Indexing) { - return new RemoteErrorObject( - "'${accessesTillHere.join()}' is an instance with type " - "${classElement.name}. " - "It can only be accessed with a field name."); - } else if (access is FieldAccess) { + if (access is FieldAccess) { String fieldName = access.fieldName; int fieldIndex = getFieldIndex(classElement, access.fieldName); if (fieldIndex == null) { @@ -735,36 +737,58 @@ Future _processVariable( } fieldIndices.add(fieldIndex); } else { - throw new UnimplementedError(); + return new RemoteErrorObject( + "'${accessesTillHere.join()}' is an instance with type " + "${classElement.name}. " + "It can only be accessed with a field name."); } } else if (value is Array) { if (access is FieldAccess) { return new RemoteErrorObject( "'${accessesTillHere.join()}' is an array with length " "${value.length}. " - "It can only be indexed with the '[index]' operation."); + "It can only be indexed with the '[index]' or " + "[start:end] operation."); } else if (access is Indexing) { int index = access.index; if (index < 0 || index >= value.length) { return new RemoteErrorObject( "${accessesTillHere.join(".")}' is an array with length " "${value.length}. " - "${index} is out of bounds"); + "${index} is out of bounds."); } fieldIndices.add(index); + } else if (access is Slice) { + startIndex = access.startIndex; + endIndex = access.endIndex; + if (endIndex == -1) { + endIndex = value.length; + } + if (startIndex < 0) { + return new RemoteErrorObject( + "In $access, the start index must be positive."); + } else if (endIndex > value.length) { + return new RemoteErrorObject( + "In $access the end-index cannot be higher than the array length " + "(${value.length})."); + } else if (startIndex > endIndex) { + return new RemoteErrorObject( + "In $access the start-index is bigger than the end-index."); + } } else { throw new UnimplementedError(); } } else { return new RemoteErrorObject("'${accessesTillHere.join()}' " "is a primitive value '${dartValueToString(value, vmContext)}' " - "and cannot not be accessed field at '${access}'"); + "and cannot not be accessed field at '${access}'."); } accessesTillHere.add(access); } if (getStructure) { return await vmContext.processLocalStructure( - frame, local.slot, fieldAccesses: fieldIndices); + frame, local.slot, fieldAccesses: fieldIndices, + startIndex: startIndex, endIndex: endIndex); } else { return await vmContext.processLocal( frame, local.slot, fieldAccesses: fieldIndices); @@ -791,7 +815,7 @@ String remoteObjectToString( message = instanceStructureToString( object.instance, object.fields, vmContext); } else if (object is RemoteArray) { - message = arrayStructureToString(object.values, vmContext); + message = arrayStructureToString(object.array, object.values, vmContext); } else { throw new UnimplementedError(); } @@ -840,12 +864,20 @@ String instanceStructureToString(InstanceStructure structure, } String arrayStructureToString( + ArrayStructure array, List items, DartinoVmContext vmContext) { StringBuffer sb = new StringBuffer(); - sb.writeln("Array with length ${items.length} ["); + sb.writeln("Array of length ${array.length} ["); + if (array.startIndex > 0) { + sb.writeln(" ... ${array.startIndex} item(s) not shown"); + } for (int i = 0; i < items.length; i++) { - sb.writeln(" $i = ${dartValueToString(items[i], vmContext)}"); + sb.writeln(" ${array.startIndex + i} = " + "${dartValueToString(items[i], vmContext)}"); + } + if (array.endIndex < array.length) { + sb.writeln(" ... ${array.length - array.endIndex} item(s) not shown"); } sb.write("]"); return '$sb'; @@ -919,12 +951,16 @@ String exceptionToString( return 'Uncaught exception: $message'; } -abstract class Access {} +abstract class Access { + // The position of this node in the source. + final int offset; + Access(this.offset); +} class LocalAccess extends Access { String localName; - LocalAccess(this.localName); + LocalAccess(int offset, this.localName) : super(offset); String toString() => "$localName"; } @@ -932,7 +968,7 @@ class LocalAccess extends Access { class FieldAccess extends Access { String fieldName; - FieldAccess(this.fieldName); + FieldAccess(int offset, this.fieldName) : super(offset); String toString() => ".$fieldName"; } @@ -940,16 +976,27 @@ class FieldAccess extends Access { class Indexing extends Access { int index; - Indexing(this.index); + Indexing(int offset, this.index) : super(offset); String toString() => "[$index]"; } + +class Slice extends Access { + int startIndex; + int endIndex; + + Slice(int offset, this.startIndex, this.endIndex) : super(offset); + + String toString() => "[$startIndex:$endIndex]"; +} + enum TokenKind { identifier, number, bracketStart, bracketEnd, + colon, dot, eof, unrecognized, @@ -965,6 +1012,7 @@ class AccessParser { AccessParser(this.text); String get kindName => kindNames[tokenKind]; + int get startOfToken => position - currentToken.length; static Map tokenPatterns = { TokenKind.identifier: new RegExp(r"[a-zA-Z_][a-zA-Z_0-9]*"), @@ -972,6 +1020,7 @@ class AccessParser { TokenKind.bracketStart: "[", TokenKind.bracketEnd: "]", TokenKind.dot: ".", + TokenKind.colon: ":", TokenKind.eof: new RegExp(r"$"), TokenKind.unrecognized: new RegExp(r"."), }; @@ -982,6 +1031,7 @@ class AccessParser { TokenKind.bracketStart: "[", TokenKind.bracketEnd: "]", TokenKind.dot: ".", + TokenKind.colon: ":", TokenKind.eof: "end of text", TokenKind.unrecognized: "unrecognized", }; @@ -993,8 +1043,9 @@ class AccessParser { return parser.result; } - void parseError(String message) { - throw new FormatException(message, text, position - currentToken.length); + void parseError(String message, {int offset}) { + offset ??= startOfToken; + throw new FormatException(message, text, offset); } void nextToken() { @@ -1018,23 +1069,33 @@ class AccessParser { parseLocal() { expect([TokenKind.identifier], "The expression to print must start with an identifier."); - result.add(new LocalAccess(currentToken)); + result.add(new LocalAccess(startOfToken, currentToken)); nextToken(); } void parseFieldAccess() { expect([TokenKind.identifier], "A field access must start with an identifier."); - result.add(new FieldAccess(currentToken)); + result.add(new FieldAccess(startOfToken, currentToken)); nextToken(); } void parseIndex() { expect([TokenKind.number], - "An indexing '[' must be followed by a number. "); - result.add(new Indexing(int.parse(currentToken))); + "An indexing '[' must be followed by a number."); + int firstIndex = int.parse(currentToken); nextToken(); - expect([TokenKind.bracketEnd], "Missing ']'"); + expect([TokenKind.bracketEnd, TokenKind.colon], "Missing ']' or ':'"); + if (tokenKind == TokenKind.bracketEnd) { + result.add(new Indexing(startOfToken, firstIndex)); + } else { + nextToken(); + expect([TokenKind.number], + "A slicing ':' must be followed by a number "); + result.add(new Slice(startOfToken, firstIndex, int.parse(currentToken))); + nextToken(); + expect([TokenKind.bracketEnd], "Missing ']' "); + } nextToken(); } @@ -1044,7 +1105,7 @@ class AccessParser { if (tokenKind == TokenKind.dot) { nextToken(); parseFieldAccess(); - } else if (tokenKind == TokenKind.bracketStart) { + } else { nextToken(); parseIndex(); } @@ -1055,5 +1116,11 @@ class AccessParser { while(tokenKind != TokenKind.eof) { parseAccess(); } + for (int i = 0; i < result.length - 1; i++) { + if (result[i] is Slice) { + parseError("Only the last operation can be a slice.", + offset: result[i].offset); + } + } } } diff --git a/pkg/dartino_compiler/lib/debug_state.dart b/pkg/dartino_compiler/lib/debug_state.dart index 2cf6bad0..96846c87 100644 --- a/pkg/dartino_compiler/lib/debug_state.dart +++ b/pkg/dartino_compiler/lib/debug_state.dart @@ -39,7 +39,6 @@ class RemoteInstance extends RemoteObject { } /// A representation of a remote instance. -// TODO(sigurdm): Send partial arrays when they are very big. See issue #536. class RemoteArray extends RemoteObject { /// An [Array] describing the remote instance. final ArrayStructure array; diff --git a/pkg/dartino_compiler/lib/vm_commands.dart b/pkg/dartino_compiler/lib/vm_commands.dart index be259ccd..d8d25c91 100644 --- a/pkg/dartino_compiler/lib/vm_commands.dart +++ b/pkg/dartino_compiler/lib/vm_commands.dart @@ -51,8 +51,13 @@ abstract class VmCommand { int fields = CommandBuffer.readInt32FromBuffer(buffer, 8); return new InstanceStructure(classId, fields); case VmCommandCode.ArrayStructure: - int length = CommandBuffer.readInt32FromBuffer(buffer, 0); - return new ArrayStructure(length); + int offset = 0; + int length = CommandBuffer.readInt32FromBuffer(buffer, offset); + offset += 4; + int startIndex = CommandBuffer.readInt32FromBuffer(buffer, offset); + offset += 4; + int endIndex = CommandBuffer.readInt32FromBuffer(buffer, offset); + return new ArrayStructure(length, startIndex, endIndex); case VmCommandCode.Instance: int classId = translateClass( CommandBuffer.readInt64FromBuffer(buffer, 0)); @@ -1218,8 +1223,25 @@ class ProcessInstanceStructure extends VmCommand { final int slot; final List fieldAccesses; - const ProcessInstanceStructure(this.frame, this.slot, this.fieldAccesses) - : super(VmCommandCode.ProcessInstanceStructure); + /// If the requested object is an array [startIndex] and [endIndex] limit the + /// number of returned elements. + /// They are ignored if the requested object is an array. + /// + /// `endIndex = -1` corresponds to requesting up to the end of the array. + /// + /// Requests for elements beyond the end of the array are cut of at the end. + /// + /// Request for elements at negative indices is an error. + final int startIndex; + final int endIndex; + + const ProcessInstanceStructure( + this.frame, + this.slot, + this.fieldAccesses, + this.startIndex, + this.endIndex) + : super(VmCommandCode.ProcessInstanceStructure); void internalAddTo( Sink> sink, @@ -1232,6 +1254,9 @@ class ProcessInstanceStructure extends VmCommand { for (int fieldAccess in fieldAccesses) { buffer.addUint32(fieldAccess); } + buffer + ..addUint32(startIndex) + ..addUint32(endIndex); buffer.sendOn(sink, code); } @@ -1584,7 +1609,10 @@ class InstanceStructure extends VmCommand { class ArrayStructure extends VmCommand { final int length; - const ArrayStructure(this.length) + final int startIndex; + final int endIndex; + + const ArrayStructure(this.length, this.startIndex, this.endIndex) : super(VmCommandCode.ArrayStructure); void internalAddTo( diff --git a/pkg/dartino_compiler/lib/vm_context.dart b/pkg/dartino_compiler/lib/vm_context.dart index faaea66b..1ccc4fdb 100644 --- a/pkg/dartino_compiler/lib/vm_context.dart +++ b/pkg/dartino_compiler/lib/vm_context.dart @@ -900,7 +900,7 @@ class DartinoVmContext { return new RemoteInstance(response, fields); } else if (response is ArrayStructure) { List values = new List(); - for (int i = 0; i < response.length; i++) { + for (int i = response.startIndex; i < response.endIndex; i++) { values.add(await readNextCommand()); } return new RemoteArray(response, values); @@ -938,10 +938,16 @@ class DartinoVmContext { int frameNumber, int localSlot, {String name, - List fieldAccesses: const []}) async { + List fieldAccesses: const [], + startIndex: 0, + endIndex: -1}) async { frameNumber ??= debugState.actualCurrentFrameNumber; await sendCommand( - new ProcessInstanceStructure(frameNumber, localSlot, fieldAccesses)); + new ProcessInstanceStructure( + frameNumber, + localSlot, fieldAccesses, + startIndex, + endIndex)); return await readStructuredObject(); } diff --git a/src/vm/session.cc b/src/vm/session.cc index c1f2c1f8..0d4a2a74 100644 --- a/src/vm/session.cc +++ b/src/vm/session.cc @@ -1060,19 +1060,33 @@ void Session::SendInstanceStructure(Instance* instance) { } } -void Session::SendArrayStructure(Array* array) { +void Session::SendArrayStructure(Array* array, int startIndex, int endIndex) { int length = array->length(); + if (endIndex == -1 || endIndex > length) { + endIndex = length; + } + // This is not strictly necessary, but makes the resulting ArrayStructure + // neater. + if (endIndex == startIndex) { + endIndex = startIndex = 0; + } + if (startIndex < 0 || startIndex > endIndex) { + SendError(Connection::kInvalidInstanceAccess); + return; + } WriteBuffer buffer; buffer.WriteInt(length); + buffer.WriteInt(startIndex); + buffer.WriteInt(endIndex); connection_->Send(Connection::kArrayStructure, buffer); - for (int i = 0; i < length; i++) { + for (int i = startIndex; i < endIndex; i++) { SendDartValue(array->get(i)); } } -void Session::SendStructure(Object* object) { +void Session::SendStructure(Object* object, int startIndex, int endIndex) { if (object->IsArray()) { - SendArrayStructure(Array::cast(object)); + SendArrayStructure(Array::cast(object), startIndex, endIndex); } else if (object->IsInstance()) { SendInstanceStructure(Instance::cast(object)); } else { @@ -1369,7 +1383,7 @@ SessionState* PausedState::ProcessMessage(Connection::Opcode opcode) { case Connection::kProcessUncaughtExceptionRequest: { Object* exception = process()->exception(); - session()->SendStructure(exception); + session()->SendStructure(exception, 0, -1); break; } @@ -1412,7 +1426,9 @@ SessionState* PausedState::ProcessMessage(Connection::Opcode opcode) { } } if (opcode == Connection::kProcessInstanceStructure) { - session()->SendStructure(object); + int startIndex = connection()->ReadInt(); + int endIndex = connection()->ReadInt(); + session()->SendStructure(object, startIndex, endIndex); } else { session()->SendDartValue(object); } diff --git a/src/vm/session.h b/src/vm/session.h index 607a2409..5194022f 100644 --- a/src/vm/session.h +++ b/src/vm/session.h @@ -214,8 +214,8 @@ class Session { void SendStackTrace(Stack* stack); void SendDartValue(Object* value); void SendInstanceStructure(Instance* instance); - void SendArrayStructure(Array* array); - void SendStructure(Object* object); + void SendArrayStructure(Array* array, int startIndex, int endIndex); + void SendStructure(Object* object, int startIndex, int endIndex); void SendProgramInfo(ClassOffsetsType* class_offsets, FunctionOffsetsType* function_offsets); void SendError(Connection::ErrorCode errorCode); diff --git a/tests/debugger/print_local_structure_expected.txt b/tests/debugger/print_local_structure_expected.txt index b1f9cb4e..5d55c1fb 100644 --- a/tests/debugger/print_local_structure_expected.txt +++ b/tests/debugger/print_local_structure_expected.txt @@ -3,8 +3,8 @@ Starting session. Type 'help' for a list of commands. > b breakHere ### set breakpoint id: '0' method: 'breakHere' bytecode index: '0' > r -tests/debugger/print_local_structure_test.dart:32:1 -32 breakHere() { } +tests/debugger/print_local_structure_test.dart:73:1 +73 breakHere() { } > f 1 > p *a Instance of 'A' { @@ -23,10 +23,6 @@ Instance of 'A' { 42 > p notExisting ### could not access 'notExisting': No local 'notExisting' in scope. -> p a.shadowMe -42 -> p a.notExisting -### could not access 'a.notExisting': 'a' has type A that does not have a field named 'notExisting'. > p a.s Instance of 'S3' > p *a.s @@ -38,15 +34,19 @@ Instance of 'S3' { S2.d: 42.42 S3.shadowMe: 0 } -> p a.s.shadowMe +> p a.s.shadowMe 0 > p a.s.shadowMe.a -### could not access 'a.s.shadowMe.a': 'a.s.shadowMe' is a primitive value '0' and cannot not be accessed field at '.a' +### could not access 'a.s.shadowMe.a': 'a.s.shadowMe' is a primitive value '0' and cannot not be accessed field at '.a'. +> p a.notExisting +### could not access 'a.notExisting': 'a' has type A that does not have a field named 'notExisting'. +> p a.shadowMe +42 > p *list._list -Array with length 3 [ - 0 = null +Array of length 3 [ + 0 = 1 1 = Instance of 'A' - 2 = null + 2 = 2 ] > p *list._list[1] Instance of 'A' { @@ -61,8 +61,85 @@ Instance of 'A' { A.f: false A.s: Instance of 'S3' } -> p *list._list.k -### could not access 'list._list.k': 'list._list' is an array with length 3. It can only be indexed with the '[index]' operation. +> p *bigList._list +Array of length 200 [ + 0 = 0 + 1 = 1 + 2 = 4 + 3 = 9 + 4 = 16 + 5 = 25 + 6 = 36 + 7 = 49 + 8 = 64 + 9 = 81 + 10 = 100 + 11 = 121 + 12 = 144 + 13 = 169 + 14 = 196 + 15 = 225 + 16 = 256 + 17 = 289 + 18 = 324 + 19 = 361 + ... 180 item(s) not shown +] +> p *list._list[1:2] +Array of length 3 [ + ... 1 item(s) not shown + 1 = Instance of 'A' + ... 1 item(s) not shown +] +> p *list._list[1:-1] +Array of length 3 [ + ... 1 item(s) not shown + 1 = Instance of 'A' + 2 = 2 +] +> p *list._list[0:2] +Array of length 3 [ + 0 = 1 + 1 = Instance of 'A' + ... 1 item(s) not shown +] +> p *list._list[1:2] +Array of length 3 [ + ... 1 item(s) not shown + 1 = Instance of 'A' + ... 1 item(s) not shown +] +> p *list._list[1:1] +Array of length 3 [ + ... 3 item(s) not shown +] +> p *bigList._list[100:102] +Array of length 200 [ + ... 100 item(s) not shown + 100 = 10000 + 101 = 10201 + ... 98 item(s) not shown +] +> p *list._list[1:2][2] +Only the last operation can be a slice. +list._list[1:2][2] + ^ +> p *list._list[1:2].a +Only the last operation can be a slice. +list._list[1:2].a + ^ +> p *list._list[-1:2] +### could not access 'list._list[-1:2]': In [-1:2], the start index must be positive. +> p *list._list[1:55] +### could not access 'list._list[1:55]': In [1:55] the end-index cannot be higher than the array length (3). +> p *list._list[3:2] +### could not access 'list._list[3:2]': In [3:2] the start-index is bigger than the end-index. +> p *list[3:2] +### could not access 'list[3:2]': 'list' is an instance with type _FixedList. It can only be accessed with a field name. +> p *list._list.[-1] +A field access must start with an identifier. Found '['. +list._list.[-1] + ^ > p *list._list.[-1] A field access must start with an identifier. Found '['. list._list.[-1] @@ -71,26 +148,38 @@ list._list.[-1] A field access must start with an identifier. Found 'number'. list._list.4 ^ +> p *list._list.4 +A field access must start with an identifier. Found 'number'. +list._list.4 + ^ +> p a[x] +An indexing '[' must be followed by a number. Found 'identifier'. +a[x] + ^ +> p *list._list.k +### could not access 'list._list.k': 'list._list' is an array with length 3. It can only be indexed with the '[index]' or [start:end] operation. +> p *list._list.k +### could not access 'list._list.k': 'list._list' is an array with length 3. It can only be indexed with the '[index]' or [start:end] operation. > p [4] The expression to print must start with an identifier. Found '['. [4] ^ -> p a[x] -An indexing '[' must be followed by a number. Found 'identifier'. -a[x] - ^ +> p %% +The expression to print must start with an identifier. Found 'unrecognized'. +%% +^ +> p [1:2] +The expression to print must start with an identifier. Found '['. +[1:2] +^ > p a[1e -Missing ']' Found 'identifier'. +Missing ']' or ':' Found 'identifier'. a[1e ^ > p a.x[1]1 Expected '.field' or '[index]' Found 'number'. a.x[1]1 ^ -> p %% -The expression to print must start with an identifier. Found 'unrecognized'. -%% -^ > p a. A field access must start with an identifier. Found 'end of text'. a. diff --git a/tests/debugger/print_local_structure_test.dart b/tests/debugger/print_local_structure_test.dart index 1fb401a8..eacd4e5a 100644 --- a/tests/debugger/print_local_structure_test.dart +++ b/tests/debugger/print_local_structure_test.dart @@ -2,7 +2,48 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE.md file. -// DartinoDebuggerCommands=b breakHere,r,f 1,p *a,p *i,p notExisting,p a.shadowMe,p a.notExisting,p a.s,p *a.s, p a.s.shadowMe,p a.s.shadowMe.a,p *list._list,p *list._list[1],p *list._list.k,p *list._list.[-1],p *list._list.4,p [4],p a[x],p a[1e,p a.x[1]1,p %%,p a.,c +// Get to the state where we can inspect the variables +// DartinoDebuggerCommands=b breakHere,r,f 1 +// Access to local +// DartinoDebuggerCommands=p *a,p *i +// DartinoDebuggerCommands=p notExisting +// Check .fieldName operation +// DartinoDebuggerCommands=p a.s +// DartinoDebuggerCommands=p *a.s +// DartinoDebuggerCommands=p a.s.shadowMe +// DartinoDebuggerCommands=p a.s.shadowMe.a +// DartinoDebuggerCommands=p a.notExisting +// Check resolution of shadowed field +// DartinoDebuggerCommands=p a.shadowMe +// List access +// DartinoDebuggerCommands=p *list._list,p *list._list[1] +// Big list should be cut at 20 elements +// DartinoDebuggerCommands=p *bigList._list +// Slicing +// DartinoDebuggerCommands=p *list._list[1:2] +// DartinoDebuggerCommands=p *list._list[1:-1] +// DartinoDebuggerCommands=p *list._list[0:2] +// DartinoDebuggerCommands=p *list._list[1:2] +// DartinoDebuggerCommands=p *list._list[1:1] +// DartinoDebuggerCommands=p *bigList._list[100:102] +// Slicing errors +// DartinoDebuggerCommands=p *list._list[1:2][2] +// DartinoDebuggerCommands=p *list._list[1:2].a +// DartinoDebuggerCommands=p *list._list[-1:2] +// DartinoDebuggerCommands=p *list._list[1:55] +// DartinoDebuggerCommands=p *list._list[3:2] +// DartinoDebuggerCommands=p *list[3:2] +// Indexing out of bounds +// DartinoDebuggerCommands=p *list._list.[-1], +// DartinoDebuggerCommands=p *list._list.4, +// Indexing with non-int +// DartinoDebuggerCommands=p a[x] +// Accessing a field of a list +// DartinoDebuggerCommands=p *list._list.k, +// Syntax errors +// DartinoDebuggerCommands=p [4],p %%,p [1:2],p a[1e,p a.x[1]1,p a. +// Continue to end of program +// DartinoDebuggerCommands=c class S0 { var str = 'spaß'; @@ -36,5 +77,8 @@ main() { var i = 42; var list = new List(3); list[1] = a; + list[2] = 2; + list[0] = 1; + var bigList = new List.generate(200, (i) => i * i, growable: false); breakHere(); }