Skip to content

Commit

Permalink
add auto-complete for mnemonics, actions for switching CPU target, an…
Browse files Browse the repository at this point in the history
…notation for opcodes which are unavailable on the current target
  • Loading branch information
mike42 committed Feb 5, 2022
1 parent c682953 commit a1a561f
Show file tree
Hide file tree
Showing 17 changed files with 375 additions and 22 deletions.
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ runPluginVerifier {
}

patchPluginXml {
changeNotes = """This change adds intention actions for converting numeric literals between hexadecimal, decimal and binary representations."""
changeNotes = """This change adds intention actions for converting numeric literals between hexadecimal, decimal and binary representations.
It also adds a completion helper for assembly language mnemonics, an inspection to indicate when unsupported mnemonics are used, and a quick-fix for switching the CPU target to one which includes the unsupported mnemonic.
The 'Generic 6502 Project' template has also been improved."""
sinceBuild = '211.6693'
}

Expand Down
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash
set -exu -o pipefail
rm -Rf build src/main/gen src/main/resources/projectTemplates/*.zip
(cd templates && zip --recurse-paths "../src/main/resources/projectTemplates/Generic 6502 Project.zip" "Generic 6502 Project/")
(cd "templates/Generic 6502 Project" && zip --recurse-paths "../../src/main/resources/projectTemplates/Generic 6502 Project.zip" root0/ .idea/)
./gradlew generateAsmLexer generateAsmParser verifyPlugin buildPlugin
./gradlew runPluginVerifier
5 changes: 3 additions & 2 deletions src/main/java/org/ca65/Asm.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,17 @@ identifierdef ::= identifierr {
imports ::= IMPORT_KEYWORD identifierdef ( COMMA identifierdef )*
dotexpr ::= DOT_KEYWORD expr
macro ::= IDENTIFIER expr
llabel ::= MNEMONIC expr?
llabel ::= instruction_mnemonic expr?

define_constant_numeric ::= identifierdef ( EQUALS ) expr
define_constant_label ::= identifierdef ( COLON_EQUALS ) expr

expr ::= anything*

anything ::= (MNEMONIC|numeric_literal|STRING_LITERAL|DOT_KEYWORD|identifierr|LABEL|EQUALS|COLON_EQUALS|COMMA|REGISTER|CHAR_LITERAL|LPAREN|RPAREN|OR|LSHIFT|RSHIFT|AND|CONSTEXPR|SHORTLABEL_REF|local_label_rref|SHORTLABEL|HIBYTE|LOBYTE|BOOLOR|BOOLAND|NOT|DIV|MUL|ADD|SUB|XOR|SCOPE_ACCESS)
anything ::= (instruction_mnemonic|numeric_literal|STRING_LITERAL|DOT_KEYWORD|identifierr|LABEL|EQUALS|COLON_EQUALS|COMMA|REGISTER|CHAR_LITERAL|LPAREN|RPAREN|OR|LSHIFT|RSHIFT|AND|CONSTEXPR|SHORTLABEL_REF|local_label_rref|SHORTLABEL|HIBYTE|LOBYTE|BOOLOR|BOOLAND|NOT|DIV|MUL|ADD|SUB|XOR|SCOPE_ACCESS)

numeric_literal ::= INT_LITERAL
instruction_mnemonic ::= MNEMONIC

identifierr ::= IDENTIFIER {
mixin="org.ca65.psi.impl.AsmIdentifierImpl"
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/org/ca65/AsmMnemonicCompletionContributor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.ca65;

import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.util.ProcessingContext;
import org.ca65.config.AsmConfiguration;
import org.ca65.helpers.Cpu;
import org.ca65.helpers.MnemonicHelper;
import org.ca65.helpers.MnemonicInfo;
import org.ca65.psi.AsmTypes;
import org.jetbrains.annotations.NotNull;

import java.awt.*;
import java.util.Set;

import static com.intellij.patterns.PlatformPatterns.psiElement;
import static org.ca65.helpers.MnemonicHelper.allMnemnonics;

public class AsmMnemonicCompletionContributor extends CompletionContributor {
public AsmMnemonicCompletionContributor() {
// Offer to auto-complete mnemonics at the start of a blank line
extend(CompletionType.BASIC, psiElement().afterLeaf(psiElement(AsmTypes.EOL_WS)), new MnemonicCompletionProvider());
}
}

class MnemonicCompletionProvider extends CompletionProvider<CompletionParameters> {
public void addCompletions(@NotNull CompletionParameters parameters,
@NotNull ProcessingContext context,
@NotNull CompletionResultSet resultSet) {
Color mnemonicColor = AsmSyntaxHighlighter.MNEMONIC.getDefaultAttributes().getForegroundColor();
Cpu projectCpu = AsmConfiguration.getInstance(parameters.getPosition().getProject()).getCpu();
Set<String> mnemonicsToShow = MnemonicHelper.getMnemonicsForCpu(projectCpu);
for(MnemonicInfo completionMnemonic : allMnemnonics) {
if(!mnemonicsToShow.contains(completionMnemonic.mnemnonic)) {
// Skip mnemonics which aren't valid for this CPU.
continue;
}
resultSet.addElement(LookupElementBuilder.create(completionMnemonic.mnemnonic)
.withItemTextForeground(mnemonicColor)
.withTailText(" " + completionMnemonic.description));
}
}
}
48 changes: 48 additions & 0 deletions src/main/java/org/ca65/action/ChangeProjectCpuIntentionAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.ca65.action;

import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.util.IntentionFamilyName;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import org.ca65.Asm6502Bundle;
import org.ca65.config.AsmConfiguration;
import org.ca65.helpers.Cpu;
import org.jetbrains.annotations.NotNull;

public class ChangeProjectCpuIntentionAction implements IntentionAction {
private final Cpu cpu;

public ChangeProjectCpuIntentionAction(Cpu cpu) {
this.cpu = cpu;
}

@Override
public @IntentionName @NotNull String getText() {
return Asm6502Bundle.message("INTN.change.cpu", cpu.name);
}

@Override
public @NotNull @IntentionFamilyName String getFamilyName() {
return Asm6502Bundle.message("INTN.NAME.change.cpu");
}

@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return true;
}

@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
AsmConfiguration.getInstance(project).setCpu(this.cpu);
DaemonCodeAnalyzer.getInstance(project).restart();
}

@Override
public boolean startInWriteAction() {
return false;
}
}
40 changes: 40 additions & 0 deletions src/main/java/org/ca65/annotator/UnsupportedMnemonicAnnotator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.ca65.annotator;

import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.lang.annotation.AnnotationBuilder;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.psi.PsiElement;
import org.ca65.action.ChangeProjectCpuIntentionAction;
import org.ca65.config.AsmConfiguration;
import org.ca65.helpers.Cpu;
import org.ca65.helpers.MnemonicHelper;
import org.ca65.psi.AsmInstructionMnemonic;
import org.jetbrains.annotations.NotNull;

public class UnsupportedMnemonicAnnotator implements Annotator {
@Override
public void annotate(@NotNull final PsiElement element, @NotNull AnnotationHolder holder) {
// Ensure the Psi Element is an expression
if (!(element instanceof AsmInstructionMnemonic)) {
return;
}
String thisMnemonic = element.getText().toLowerCase();
Cpu projectCpu = AsmConfiguration.getInstance(element.getProject()).getCpu();
if (MnemonicHelper.getMnemonicsForCpu(projectCpu).contains(thisMnemonic)) {
return; // All is good
}
AnnotationBuilder builder = holder.newAnnotation(HighlightSeverity.ERROR, "The '" + thisMnemonic + "' instruction is not available on the " + projectCpu.name + " CPU")
.range(element.getTextRange())
.highlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
// Suggest alternative CPU setting if we have another CPU config which contains this
if (MnemonicHelper.getMnemonicsForCpu(Cpu.CPU_65C02).contains(thisMnemonic)) {
builder = builder.withFix(new ChangeProjectCpuIntentionAction(Cpu.CPU_65C02));
} else if(MnemonicHelper.getMnemonicsForCpu(Cpu.CPU_65C816).contains(thisMnemonic)) {
// Suggest 65C816 only if instruction is not available on 65C02!
builder = builder.withFix(new ChangeProjectCpuIntentionAction(Cpu.CPU_65C816));
}
builder.create();
}
}
48 changes: 48 additions & 0 deletions src/main/java/org/ca65/config/AsmConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.ca65.config;

import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.project.Project;
import com.intellij.util.xmlb.annotations.XMap;
import org.ca65.helpers.Cpu;
import org.jetbrains.annotations.NotNull;

@State(name = "AsmConfiguration",
storages = @Storage("asm_6502.xml"))
public class AsmConfiguration implements PersistentStateComponent<AsmConfiguration.State> {
public static class State {
@XMap(entryTagName = "cpu")
public String cpu;
}

private AsmConfiguration.State myState = new State();

public static AsmConfiguration getInstance(Project project) {
return project.getService(AsmConfiguration.class);
}

@Override
public AsmConfiguration.State getState() {
return myState;
}

@Override
public void loadState(@NotNull AsmConfiguration.State state) {
myState = state;
}

public Cpu getCpu() {
for(Cpu item : Cpu.values()) {
if(item.toString().equals(this.myState.cpu)) {
return item;
}
}
// Default to classic 6502
return Cpu.CPU_6502;
}

public void setCpu(Cpu newCpu) {
this.myState.cpu = newCpu.toString();
}
}
13 changes: 13 additions & 0 deletions src/main/java/org/ca65/helpers/Cpu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.ca65.helpers;

public enum Cpu {
CPU_6502("6502"),
CPU_65C02("65C02"),
CPU_65C816("65C816");

Cpu(String name) {
this.name = name;
}

public final String name;
}
148 changes: 148 additions & 0 deletions src/main/java/org/ca65/helpers/MnemonicHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package org.ca65.helpers;

import java.util.Set;

public class MnemonicHelper {
public static MnemonicInfo[] allMnemnonics = new MnemonicInfo[] {
new MnemonicInfo("adc", "Add Memory to Accumulator with Carry"),
new MnemonicInfo("and", "\"AND\" Memory with Accumulator"),
new MnemonicInfo("asl", "Shift One Bit Left, Memory or Accumulator"),
new MnemonicInfo("bbr0", "Branch on bit 0 reset"),
new MnemonicInfo("bbr1", "Branch on bit 1 reset"),
new MnemonicInfo("bbr2", "Branch on bit 2 reset"),
new MnemonicInfo("bbr3", "Branch on bit 3 reset"),
new MnemonicInfo("bbr4", "Branch on bit 4 reset"),
new MnemonicInfo("bbr5", "Branch on bit 5 reset"),
new MnemonicInfo("bbr6", "Branch on bit 6 reset"),
new MnemonicInfo("bbr7", "Branch on bit 7 reset"),
new MnemonicInfo("bbs0", "Branch on bit 0 set"),
new MnemonicInfo("bbs1", "Branch on bit 1 set"),
new MnemonicInfo("bbs2", "Branch on bit 2 set"),
new MnemonicInfo("bbs3", "Branch on bit 3 set"),
new MnemonicInfo("bbs4", "Branch on bit 4 set"),
new MnemonicInfo("bbs5", "Branch on bit 5 set"),
new MnemonicInfo("bbs6", "Branch on bit 6 set"),
new MnemonicInfo("bbs7", "Branch on bit 7 set"),
new MnemonicInfo("bcc", "Branch on Carry Clear (C=0)"),
new MnemonicInfo("bcs", "Branch on Carry Set (C=1)"),
new MnemonicInfo("beq", "Branch if Equal (Z=1)"),
new MnemonicInfo("bit", "Bit Test"),
new MnemonicInfo("bmi", "Branch if Result Minus (N=1)"),
new MnemonicInfo("bne", "Branch if Not Equal (Z=0)"),
new MnemonicInfo("bpl", "Branch if Result Plus (N=0)"),
new MnemonicInfo("bra", "Branch Always"),
new MnemonicInfo("brk", "Force Break"),
new MnemonicInfo("brl", "Branch Always Long"),
new MnemonicInfo("bvc", "Branch on Overflow Clear (V=0)"),
new MnemonicInfo("bvs", "Branch on Overflow Set (V=1)"),
new MnemonicInfo("clc", "Clear Carry Flag"),
new MnemonicInfo("cld", "Clear Decimal Mode"),
new MnemonicInfo("cli", "Clear Interrupt Disable Bit"),
new MnemonicInfo("clv", "Clear Overflow Flag"),
new MnemonicInfo("cmp", "Compare Memory and Accumulator"),
new MnemonicInfo("cop", "Coprocessor"),
new MnemonicInfo("cpx", "Compare Memory and Index X"),
new MnemonicInfo("cpy", "Compare Memory and Index Y"),
new MnemonicInfo("dec", "Decrement Memory or Accumulator by One"),
new MnemonicInfo("dex", "Decrement Index X by One"),
new MnemonicInfo("dey", "Decrement Index Y by One"),
new MnemonicInfo("eor", "\"Exclusive OR\" Memory with Accumulator"),
new MnemonicInfo("inc", "Increment Memory or Accumulator by One"),
new MnemonicInfo("inx", "Increment Index X by One"),
new MnemonicInfo("iny", "Increment Index Y by One"),
new MnemonicInfo("jml", "Jump Long"),
new MnemonicInfo("jmp", "Jump to New Location"),
new MnemonicInfo("jsl", "Jump Subroutine Long"),
new MnemonicInfo("jsr", "Jump to News Location Saving Return"),
new MnemonicInfo("lda", "Load Accumulator with Memory"),
new MnemonicInfo("ldx", "Load Index X with Memory"),
new MnemonicInfo("ldy", "Load Index Y with Memory"),
new MnemonicInfo("lsr", "Shift One Bit Right (Memory or Accumulator)"),
new MnemonicInfo("mvn", "Block Move Negative"),
new MnemonicInfo("mvp", "Block Move Positive"),
new MnemonicInfo("nop", "No Operation"),
new MnemonicInfo("ora", "\"OR\" Memory with Accumulator"),
new MnemonicInfo("pea", "Push Absolute Address"),
new MnemonicInfo("pei", "Push Indirect Address"),
new MnemonicInfo("per", "Push Program Counter Relative Address"),
new MnemonicInfo("pha", "Push Accumulator on Stack"),
new MnemonicInfo("phb", "Push Data Bank Register on Stack"),
new MnemonicInfo("phd", "Push Direct Register on Stack"),
new MnemonicInfo("phk", "Push Program Bank Register on Stack"),
new MnemonicInfo("php", "Push Processor Status on Stack"),
new MnemonicInfo("phx", "Push Index X on Stack"),
new MnemonicInfo("phy", "Push Index Y on Stack"),
new MnemonicInfo("pla", "Pull Accumulator from Stack"),
new MnemonicInfo("plb", "Pull Data Bank Register from Stack"),
new MnemonicInfo("pld", "Pull Direct Register from Stack"),
new MnemonicInfo("plp", "Pull Processor Status from Stack"),
new MnemonicInfo("plx", "Pull Index X from Stack"),
new MnemonicInfo("ply", "Pull Index Y from Stack"),
new MnemonicInfo("rep", "Reset Status Bits"),
new MnemonicInfo("rmb0", "Reset Memory Bit 0"),
new MnemonicInfo("rmb1", "Reset Memory Bit 1"),
new MnemonicInfo("rmb2", "Reset Memory Bit 2"),
new MnemonicInfo("rmb3", "Reset Memory Bit 3"),
new MnemonicInfo("rmb4", "Reset Memory Bit 4"),
new MnemonicInfo("rmb5", "Reset Memory Bit 5"),
new MnemonicInfo("rmb6", "Reset Memory Bit 6"),
new MnemonicInfo("rmb7", "Reset Memory Bit 7"),
new MnemonicInfo("rol", "Rotate One Bit Left (Memory or Accumulator)"),
new MnemonicInfo("ror", "Rotate One Bit Right"),
new MnemonicInfo("rti", "Return from Interrupt"),
new MnemonicInfo("rtl", "Return from Subroutine Long"),
new MnemonicInfo("rts", "Return from Subroutine"),
new MnemonicInfo("sbc", "Subtract Memory from Accumulator"),
new MnemonicInfo("sep", "Set Processor Status Bit"),
new MnemonicInfo("sec", "Set Carry Flag"),
new MnemonicInfo("sed", "Set Decimal Mode"),
new MnemonicInfo("sei", "Set Interrupt Disable Status"),
new MnemonicInfo("smb0", "Set Memory Bit 0"),
new MnemonicInfo("smb1", "Set Memory Bit 1"),
new MnemonicInfo("smb2", "Set Memory Bit 2"),
new MnemonicInfo("smb3", "Set Memory Bit 3"),
new MnemonicInfo("smb4", "Set Memory Bit 4"),
new MnemonicInfo("smb5", "Set Memory Bit 5"),
new MnemonicInfo("smb6", "Set Memory Bit 6"),
new MnemonicInfo("smb7", "Set Memory Bit 7"),
new MnemonicInfo("sta", "Store Accumulator in Memory"),
new MnemonicInfo("stp", "Stop the Clock"),
new MnemonicInfo("stx", "Store Index X in Memory"),
new MnemonicInfo("sty", "Store Index Y in Memory"),
new MnemonicInfo("stz", "Store Zero in Memory"),
new MnemonicInfo("tax", "Transfer Accumulator in Index X"),
new MnemonicInfo("tay", "Transfer Accumulator to Index Y"),
new MnemonicInfo("tcd", "Transfer C Accumulator to Direct Register"),
new MnemonicInfo("tcs", "Transfer C Accumulator to Stack Pointer"),
new MnemonicInfo("tdc", "Transfer Direct Register to C Accumulator"),
new MnemonicInfo("trb", "Test and Reset Bit"),
new MnemonicInfo("tsb", "Test and Set Bit"),
new MnemonicInfo("tsc", "Transfer Stack Pointer to C Accumulator"),
new MnemonicInfo("tsx", "Transfer Stack Pointer Register to Index X"),
new MnemonicInfo("txa", "Transfer Index X to Accumulator"),
new MnemonicInfo("txs", "Transfer Index X to Stack Pointer Register"),
new MnemonicInfo("txy", "Transfer Index X to Index Y"),
new MnemonicInfo("tya", "Transfer Index Y to Accumulator"),
new MnemonicInfo("tyx", "Transfer Index Y to Index X"),
new MnemonicInfo("wai", "Wait for Interrupt"),
new MnemonicInfo("wdm", "Reserved for future use"),
new MnemonicInfo("xba", "Exchange B and A Accumulator"),
new MnemonicInfo("xce", "Exchange Carry and Emulation Bits"),
};

public static Set<String> validMnemnonics65C816 = Set.of("adc", "and", "asl", "bcc", "bcs", "beq", "bit", "bmi", "bne", "bpl", "bra", "brk", "brl", "bvc", "bvs", "clc", "cld", "cli", "clv", "cmp", "cop", "cpx", "cpy", "dec", "dex", "dey", "eor", "inc", "inx", "iny", "jml", "jmp", "jsl", "jsr", "lda", "ldx", "ldy", "lsr", "mvn", "mvp", "nop", "ora", "pea", "pei", "per", "pha", "phb", "phd", "phk", "php", "phx", "phy", "pla", "plb", "pld", "plp", "plx", "ply", "rep", "rol", "ror", "rti", "rtl", "rts", "sbc", "sep", "sec", "sed", "sei", "sta", "stp", "stx", "sty", "stz", "tax", "tay", "tcd", "tcs", "tdc", "trb", "tsb", "tsc", "tsx", "txa", "txs", "txy", "tya", "tyx", "wai", "wdm", "xba", "xce");
public static Set<String> validMnemnonics65C02 = Set.of("adc", "and", "asl", "bbr0", "bbr1", "bbr2", "bbr3", "bbr4", "bbr5", "bbr6", "bbr7", "bbs0", "bbs1", "bbs2", "bbs3", "bbs4", "bbs5", "bbs6", "bbs7", "bcc", "bcs", "beq", "bit", "bmi", "bne", "bpl", "bra", "brk", "bvc", "bvs", "clc", "cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey", "eor", "inc", "inx", "iny", "jmp", "jsr", "lda", "ldx", "ldy", "lsr", "nop", "ora", "pha", "php", "phx", "phy", "pla", "plp", "plx", "ply", "rmb0", "rmb1", "rmb2", "rmb3", "rmb4", "rmb5", "rmb6", "rmb7", "rol", "ror", "rti", "rts", "sbc", "sec", "sed", "sei", "smb0", "smb1", "smb2", "smb3", "smb4", "smb5", "smb6", "smb7", "sta", "stp", "stx", "sty", "stz", "tax", "tay", "trb", "tsb", "tsx", "txa", "txs", "tya", "wai");
public static Set<String> validMnemnonics6502 = Set.of("adc", "and", "asl", "bcc", "bcs", "beq", "bit", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc", "cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey", "eor", "inc", "inx", "iny", "jmp", "jsr", "lda", "ldx", "ldy", "lsr", "nop", "ora", "pha", "php", "pla", "plp", "ror", "rti", "rts", "sbc", "sec", "sed", "sei", "sta", "stx", "sty", "tax", "tay", "tsx", "txa", "txs", "tya");

public static Set<String> getMnemonicsForCpu(Cpu cpu) {
switch (cpu) {
case CPU_6502:
return validMnemnonics6502;
case CPU_65C02:
return validMnemnonics65C02;
case CPU_65C816:
return validMnemnonics65C816;
}
throw new UnsupportedOperationException("Unknown CPU " + cpu);
}
}
Loading

0 comments on commit a1a561f

Please sign in to comment.