Skip to content
Snippets Groups Projects
Commit fb9f43e6 authored by dgelessus's avatar dgelessus
Browse files

Unify handling of command-less input in ProBKernel

There is now a single preprocessInput method that is responsible for
adding a command prefix to any input that doesn't already have one.
This simplifies the implementations of (eval|inspect|complete)Internal
somewhat, because they can now assume that the input (after
preprocessing) always starts with a command name.
parent d083be85
No related branches found
No related tags found
No related merge requests found
......@@ -322,27 +322,47 @@ public final class ProBKernel extends BaseKernel {
return MACHINE_CODE_PATTERN.matcher(code).matches();
}
private @Nullable DisplayData evalInternal(final @NotNull PositionedString code) {
/**
* Preprocess the given input by ensuring that it starts with a command name.
* If a command name is already present,
* the input is returned unchanged.
* Otherwise,
* an appropriate command is added based on the type of input.
*
* @param code the input code to preprocess
* @return the input with a command name prefixed if necessary
*/
private static @NotNull PositionedString preprocessInput(final @NotNull PositionedString code) {
final Matcher commandMatcher = COMMAND_PATTERN.matcher(code.getValue());
final PositionedString name;
final PositionedString argString;
final String prefix;
if (commandMatcher.matches()) {
// The input is a command, execute it directly.
name = code.substring(commandMatcher.start(1), commandMatcher.end(1));
if (commandMatcher.group(2) == null) {
argString = code.substring(code.getValue().length());
// The input already includes a command, so no prefix needs to be added.
prefix = "";
} else if (isMachineCode(code.getValue())) {
// The input appears to be a machine, add a command to load it.
prefix = "::load\n";
} else {
argString = code.substring(commandMatcher.start(2), commandMatcher.end(2));
// By default, assume that the input is an expression that should be evaluated.
prefix = ":eval ";
}
} else if (isMachineCode(code.getValue())) {
// The input appears to be a machine, load it.
// The leading newline here is important. ::load expects the first input line to contain preference assignments; the actual machine code has to start on the second line.
name = new PositionedString("::load", code.getStartPosition() - 7);
argString = new PositionedString("\n" + code.getValue(), code.getStartPosition() - 1);
// Add the prefix and adjust the start position.
// This means that the characters from the prefix (if any) will have negative positions,
// but the characters from the real source code will have the same positions as before.
return new PositionedString(prefix + code.getValue(), code.getStartPosition() - prefix.length());
}
private @Nullable DisplayData evalInternal(final @NotNull PositionedString code) {
final PositionedString preprocessedCode = preprocessInput(code);
final Matcher commandMatcher = COMMAND_PATTERN.matcher(preprocessedCode.getValue());
if (!commandMatcher.matches()) {
throw new AssertionError("Preprocessed input does not include a command - this should not happen");
}
final PositionedString name = preprocessedCode.substring(commandMatcher.start(1), commandMatcher.end(1));
final PositionedString argString;
if (commandMatcher.group(2) == null) {
argString = preprocessedCode.substring(preprocessedCode.getValue().length());
} else {
// By default, assume that the input is an expression and evaluate it.
name = new PositionedString(":eval", code.getStartPosition() - 6);
argString = code;
argString = preprocessedCode.substring(commandMatcher.start(2), commandMatcher.end(2));
}
return this.executeCommand(name, argString);
}
......@@ -378,16 +398,18 @@ public final class ProBKernel extends BaseKernel {
}
private @Nullable DisplayData inspectInternal(final @NotNull PositionedString code, final int at) {
final Matcher commandMatcher = COMMAND_PATTERN.matcher(code.getValue());
if (commandMatcher.matches()) {
// The code is a valid command.
final PositionedString preprocessedCode = preprocessInput(code);
final Matcher commandMatcher = COMMAND_PATTERN.matcher(preprocessedCode.getValue());
if (!commandMatcher.matches()) {
throw new AssertionError("Preprocessed input does not include a command - this should not happen");
}
final String name = commandMatcher.group(1);
if (this.getCommands().containsKey(name)) {
final Command command = this.getCommands().get(name);
if (at <= commandMatcher.end(1)) {
if (at <= preprocessedCode.getStartPosition() + commandMatcher.end(1)) {
// The cursor is somewhere in the command name, show help text for the command.
return command.renderHelp();
} else if (at < commandMatcher.start(2)) {
} else if (at < preprocessedCode.getStartPosition() + commandMatcher.start(2)) {
// The cursor is in the whitespace between the command name and arguments, don't show anything.
return null;
} else {
......@@ -395,9 +417,9 @@ public final class ProBKernel extends BaseKernel {
assert name != null;
final PositionedString argString;
if (commandMatcher.group(2) == null) {
argString = code.substring(code.getValue().length());
argString = preprocessedCode.substring(preprocessedCode.getValue().length());
} else {
argString = code.substring(commandMatcher.start(2), commandMatcher.end(2));
argString = preprocessedCode.substring(commandMatcher.start(2), commandMatcher.end(2));
}
return inspectCommandArguments(command, argString, at);
}
......@@ -405,10 +427,6 @@ public final class ProBKernel extends BaseKernel {
// Invalid command, can't inspect.
return null;
}
} else {
// The code is not a valid command, ask :eval to inspect.
return inspectCommandArguments(this.getCommands().get(":eval"), code, at);
}
}
@Override
......@@ -444,18 +462,20 @@ public final class ProBKernel extends BaseKernel {
}
private @Nullable ReplacementOptions completeInternal(final @NotNull PositionedString code, final int at) {
final Matcher commandMatcher = COMMAND_PATTERN.matcher(code.getValue());
if (commandMatcher.matches()) {
// The code is a valid command.
if (at <= commandMatcher.end(1)) {
final PositionedString preprocessedCode = preprocessInput(code);
final Matcher commandMatcher = COMMAND_PATTERN.matcher(preprocessedCode.getValue());
if (!commandMatcher.matches()) {
throw new AssertionError("Preprocessed input does not include a command - this should not happen");
}
if (at <= preprocessedCode.getStartPosition() + commandMatcher.end(1)) {
// The cursor is somewhere in the command name, provide command completions.
final String prefix = code.substring(commandMatcher.start(1), at).getValue();
final String prefix = preprocessedCode.substring(commandMatcher.start(1), at).getValue();
return new ReplacementOptions(
this.getCommands().keySet().stream().filter(s -> s.startsWith(prefix)).sorted().collect(Collectors.toList()),
commandMatcher.start(1),
commandMatcher.end(1)
);
} else if (at < commandMatcher.start(2)) {
} else if (at < preprocessedCode.getStartPosition() + commandMatcher.start(2)) {
// The cursor is in the whitespace between the command name and arguments, don't show anything.
return null;
} else {
......@@ -464,9 +484,9 @@ public final class ProBKernel extends BaseKernel {
assert name != null;
final PositionedString argString;
if (commandMatcher.group(2) == null) {
argString = code.substring(code.getValue().length());
argString = preprocessedCode.substring(preprocessedCode.getValue().length());
} else {
argString = code.substring(commandMatcher.start(2), commandMatcher.end(2));
argString = preprocessedCode.substring(commandMatcher.start(2), commandMatcher.end(2));
}
if (this.getCommands().containsKey(name)) {
return completeCommandArguments(this.getCommands().get(name), argString, at);
......@@ -475,10 +495,6 @@ public final class ProBKernel extends BaseKernel {
return null;
}
}
} else {
// The code is not a valid command, ask :eval for completions.
return completeCommandArguments(this.getCommands().get(":eval"), code, at);
}
}
@Override
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment