mirror of
https://github.com/AppleCommander/bastools.git
synced 2025-01-23 11:30:24 +00:00
Optimization to move constant values into variables. Closes #4.
This commit is contained in:
parent
187c926017
commit
4d6e608f59
@ -2,14 +2,20 @@ package io.github.applecommander.bastokenizer.api;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.github.applecommander.bastokenizer.api.optimizations.ExtractConstantValues;
|
||||
import io.github.applecommander.bastokenizer.api.optimizations.MergeLines;
|
||||
import io.github.applecommander.bastokenizer.api.optimizations.RemoveEmptyStatements;
|
||||
import io.github.applecommander.bastokenizer.api.optimizations.RemoveRemStatements;
|
||||
import io.github.applecommander.bastokenizer.api.optimizations.Renumber;
|
||||
|
||||
/**
|
||||
* All optimization capabilities are definined here in the "best" manner of execution.
|
||||
* Essentially, the goal is to prioritize the optimizations to manage dependencies.
|
||||
*/
|
||||
public enum Optimization {
|
||||
REMOVE_EMPTY_STATEMENTS(RemoveEmptyStatements::new),
|
||||
REMOVE_REM_STATEMENTS(RemoveRemStatements::new),
|
||||
EXTRACT_CONSTANT_VALUES(ExtractConstantValues::new),
|
||||
MERGE_LINES(MergeLines::new),
|
||||
RENUMBER(Renumber::new);
|
||||
|
||||
|
@ -19,6 +19,10 @@ public class Line {
|
||||
this.program = program;
|
||||
}
|
||||
|
||||
public int getLineNumber() {
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
public Optional<Line> nextLine() {
|
||||
int i = program.lines.indexOf(this);
|
||||
if (i == -1 || i+1 >= program.lines.size()) {
|
||||
|
@ -0,0 +1,149 @@
|
||||
package io.github.applecommander.bastokenizer.api.optimizations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.github.applecommander.bastokenizer.api.Configuration;
|
||||
import io.github.applecommander.bastokenizer.api.Visitors;
|
||||
import io.github.applecommander.bastokenizer.api.model.ApplesoftKeyword;
|
||||
import io.github.applecommander.bastokenizer.api.model.Line;
|
||||
import io.github.applecommander.bastokenizer.api.model.Program;
|
||||
import io.github.applecommander.bastokenizer.api.model.Statement;
|
||||
import io.github.applecommander.bastokenizer.api.model.Token;
|
||||
import io.github.applecommander.bastokenizer.api.utils.VariableNameGenerator;
|
||||
import io.github.applecommander.bastokenizer.api.visitors.VariableCollectorVisitor;
|
||||
|
||||
/**
|
||||
* Find constants and extract to variables in order to have the number parsed only once.
|
||||
*/
|
||||
public class ExtractConstantValues extends BaseVisitor {
|
||||
/** These trigger the start of a replacement range. Note the special logic for assignments. */
|
||||
public static List<ApplesoftKeyword> TARGET_STARTS = Arrays.asList(
|
||||
ApplesoftKeyword.FOR, ApplesoftKeyword.CALL, ApplesoftKeyword.PLOT, ApplesoftKeyword.HLIN,
|
||||
ApplesoftKeyword.VLIN, ApplesoftKeyword.HCOLOR, ApplesoftKeyword.HPLOT, ApplesoftKeyword.DRAW,
|
||||
ApplesoftKeyword.XDRAW, ApplesoftKeyword.HTAB, ApplesoftKeyword.SCALE, ApplesoftKeyword.COLOR,
|
||||
ApplesoftKeyword.VTAB, ApplesoftKeyword.HIMEM, ApplesoftKeyword.LOMEM, ApplesoftKeyword.SPEED,
|
||||
ApplesoftKeyword.LET, ApplesoftKeyword.IF, ApplesoftKeyword.ON, ApplesoftKeyword.WAIT,
|
||||
ApplesoftKeyword.POKE);
|
||||
/** These trigger the end of a replacement range. End of statement is always an end. */
|
||||
public static List<ApplesoftKeyword> TARGET_ENDS = Arrays.asList(
|
||||
ApplesoftKeyword.GOTO, ApplesoftKeyword.GOSUB, ApplesoftKeyword.THEN);
|
||||
|
||||
// Map keyed by value (Double isn't a good key, using a String of the number) and pointing to replacement variable name
|
||||
private Map<String,String> map = new HashMap<>();
|
||||
|
||||
private VariableNameGenerator variableGenerator = new VariableNameGenerator();
|
||||
private Set<String> existingVariables;
|
||||
private Function<Token,Token> consumer = this::nullTransformation;
|
||||
|
||||
public ExtractConstantValues(Configuration config) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
public Token nullTransformation(Token token) {
|
||||
return token;
|
||||
}
|
||||
/** Collect a map of constant values and the new variable name to be used. */
|
||||
public Token numberToIdentTransformation(Token token) {
|
||||
String key = token.number.toString();
|
||||
// New entry, create it
|
||||
if (!map.containsKey(key)) {
|
||||
String varName = null;
|
||||
do {
|
||||
varName = variableGenerator.get()
|
||||
.orElseThrow(() -> new RuntimeException("Ran out of variable names to assign"));
|
||||
} while (existingVariables.contains(varName));
|
||||
map.put(key, varName);
|
||||
}
|
||||
// Existing (or NEW!) entry, swap to that variable.
|
||||
if (map.containsKey(key)) {
|
||||
return Token.ident(token.line, map.get(key));
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Program visit(Program program) {
|
||||
VariableCollectorVisitor collector = Visitors.variableCollectorVisitor();
|
||||
program.accept(collector);
|
||||
this.existingVariables = collector.getVariableNames();
|
||||
|
||||
program = super.visit(program);
|
||||
|
||||
injectLine0(program);
|
||||
|
||||
return program;
|
||||
}
|
||||
private void injectLine0(Program program) {
|
||||
Line line = generateLine0(program);
|
||||
// setup a renumber of lines that interfere if we have any
|
||||
if (program.lines.get(0).lineNumber == 0) {
|
||||
// start with line #0 should become line #1
|
||||
super.reassignments.put(0, 1);
|
||||
// chase it to the end!
|
||||
program.lines.stream()
|
||||
.map(Line::getLineNumber)
|
||||
.filter(super.reassignments::containsValue)
|
||||
.forEach(n -> { super.reassignments.put(n, n+1); });
|
||||
}
|
||||
program.lines.add(0, line);
|
||||
}
|
||||
private Line generateLine0(Program program) {
|
||||
Line line = new Line(0, program);
|
||||
map.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByValue())
|
||||
.map(this::toStatement)
|
||||
.forEach(line.statements::add);
|
||||
return line;
|
||||
}
|
||||
private Statement toStatement(Map.Entry<String,String> variable) {
|
||||
Statement statement = new Statement();
|
||||
statement.tokens.add(Token.ident(-1, variable.getValue()));
|
||||
statement.tokens.add(Token.syntax(-1, '='));
|
||||
statement.tokens.add(Token.number(-1, Double.valueOf(variable.getKey())));
|
||||
return statement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement visit(Statement statement) {
|
||||
try {
|
||||
if (!statement.tokens.isEmpty()) {
|
||||
int size = statement.tokens.size();
|
||||
Token t = statement.tokens.get(0);
|
||||
// Special logic for "A=5+1" while trying to skip constant forms of "A=1234" (don't replicate)
|
||||
if (t.type == Token.Type.IDENT && size > 3) {
|
||||
this.consumer = this::numberToIdentTransformation;
|
||||
}
|
||||
// Special logic for "LET A=5+1" while trying to skip constant forms of "LET A=1234" (don't replicate)
|
||||
if (t.type == Token.Type.KEYWORD && t.keyword == ApplesoftKeyword.LET && size > 4) {
|
||||
this.consumer = this::numberToIdentTransformation;
|
||||
}
|
||||
}
|
||||
return super.visit(statement);
|
||||
} finally {
|
||||
this.consumer = this::nullTransformation;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token visit(Token token) {
|
||||
switch (token.type) {
|
||||
case KEYWORD:
|
||||
if (TARGET_STARTS.contains(token.keyword)) {
|
||||
this.consumer = this::numberToIdentTransformation;
|
||||
} else if (TARGET_ENDS.contains(token.keyword)) {
|
||||
this.consumer = this::nullTransformation;
|
||||
}
|
||||
break;
|
||||
case NUMBER:
|
||||
return this.consumer.apply(token);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return super.visit(token);
|
||||
}
|
||||
}
|
@ -78,6 +78,7 @@ public class Main implements Callable<Void> {
|
||||
"Enable specific optimizations.",
|
||||
"* @|green remove-empty-statements|@ - Strip out all '::'-like statements.",
|
||||
"* @|green remove-rem-statements|@ - Remove all REM statements.",
|
||||
"* @|green extract-constant-values|@ - Assign all constant values first.",
|
||||
"* @|green merge-lines|@ - Merge lines.",
|
||||
"* @|green renumber|@ - Renumber program."
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user