Skip to content

Commit

Permalink
Support for inspecting a partial array in the cli-debugger.
Browse files Browse the repository at this point in the history
By default printing an array structure will cut the array at 20 elements.
By using a slicing syntax different sub-arrays can be requested.

[email protected]

Review URL: https://codereview.chromium.org/2075423003 .
  • Loading branch information
sigurdm committed Jun 20, 2016
1 parent 2a03a33 commit ae989f8
Show file tree
Hide file tree
Showing 8 changed files with 315 additions and 66 deletions.
119 changes: 93 additions & 26 deletions pkg/dartino_compiler/lib/cli_debugger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import "vm_context.dart";

import 'vm_commands.dart' show
Array,
ArrayStructure,
ClassValue,
ConnectionError,
DartValue,
Expand Down Expand Up @@ -84,6 +85,8 @@ Commands:
'q/quit' quit the session
""";

const int arrayItemsShownByDefault = 20;

class CommandLineDebugger {
final DartinoVmContext vmContext;
final Stream<String> stream;
Expand Down Expand Up @@ -711,6 +714,10 @@ Future<RemoteObject> _processVariable(

List<int> fieldIndices = new List<int>();
List<Access> accessesTillHere = <Access>[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,
Expand All @@ -720,12 +727,7 @@ Future<RemoteObject> _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) {
Expand All @@ -735,36 +737,58 @@ Future<RemoteObject> _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);
Expand All @@ -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();
}
Expand Down Expand Up @@ -840,12 +864,20 @@ String instanceStructureToString(InstanceStructure structure,
}

String arrayStructureToString(
ArrayStructure array,
List<DartValue> 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';
Expand Down Expand Up @@ -919,37 +951,52 @@ 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";
}

class FieldAccess extends Access {
String fieldName;

FieldAccess(this.fieldName);
FieldAccess(int offset, this.fieldName) : super(offset);

String toString() => ".$fieldName";
}

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,
Expand All @@ -965,13 +1012,15 @@ class AccessParser {
AccessParser(this.text);

String get kindName => kindNames[tokenKind];
int get startOfToken => position - currentToken.length;

static Map<TokenKind, Pattern> tokenPatterns = {
TokenKind.identifier: new RegExp(r"[a-zA-Z_][a-zA-Z_0-9]*"),
TokenKind.number: new RegExp(r"-?[0-9]+"),
TokenKind.bracketStart: "[",
TokenKind.bracketEnd: "]",
TokenKind.dot: ".",
TokenKind.colon: ":",
TokenKind.eof: new RegExp(r"$"),
TokenKind.unrecognized: new RegExp(r"."),
};
Expand All @@ -982,6 +1031,7 @@ class AccessParser {
TokenKind.bracketStart: "[",
TokenKind.bracketEnd: "]",
TokenKind.dot: ".",
TokenKind.colon: ":",
TokenKind.eof: "end of text",
TokenKind.unrecognized: "unrecognized",
};
Expand All @@ -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() {
Expand All @@ -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();
}

Expand All @@ -1044,7 +1105,7 @@ class AccessParser {
if (tokenKind == TokenKind.dot) {
nextToken();
parseFieldAccess();
} else if (tokenKind == TokenKind.bracketStart) {
} else {
nextToken();
parseIndex();
}
Expand All @@ -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);
}
}
}
}
1 change: 0 additions & 1 deletion pkg/dartino_compiler/lib/debug_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
38 changes: 33 additions & 5 deletions pkg/dartino_compiler/lib/vm_commands.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -1218,8 +1223,25 @@ class ProcessInstanceStructure extends VmCommand {
final int slot;
final List<int> 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<List<int>> sink,
Expand All @@ -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);
}

Expand Down Expand Up @@ -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(
Expand Down
12 changes: 9 additions & 3 deletions pkg/dartino_compiler/lib/vm_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,7 @@ class DartinoVmContext {
return new RemoteInstance(response, fields);
} else if (response is ArrayStructure) {
List<DartValue> values = new List<DartValue>();
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);
Expand Down Expand Up @@ -938,10 +938,16 @@ class DartinoVmContext {
int frameNumber,
int localSlot,
{String name,
List<int> fieldAccesses: const <int>[]}) async {
List<int> fieldAccesses: const <int>[],
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();
}

Expand Down
Loading

0 comments on commit ae989f8

Please sign in to comment.