6 Commits
32 changed files with 848 additions and 112 deletions
+34 -4
View File
@@ -1,4 +1,4 @@
# MC68000 Assembly Language Plugin [![Build Status](https://app.travis-ci.com/chrisly42/mc68000-asm-plugin.svg?branch=master)](https://app.travis-ci.com/chrisly42/mc68000-asm-plugin) [![Coverage Status](https://coveralls.io/repos/github/chrisly42/mc68000-asm-plugin/badge.svg?branch=main)](https://coveralls.io/github/chrisly42/mc68000-asm-plugin?branch=main)
# MC68000 Assembly Language Plugin
_MC68000 Assembly Language Plugin_ is plugin for Jetbrains IDEs (CLion, IntelliJ, etc.).
@@ -84,6 +84,17 @@ optimize the order of execution.
Points out unresolved references such for global and local labels, macros or symbols. Right now, missing symbol and global label references are shown only as
weak warnings as missing macro evaluation will not resolve symbols defined via `STRUCT` macros.
#### M68kAddressRegisterWordAccessInspection - Suspicious word access to address register
Warns on `movea.w` to address registers which is often a typo and takes ages to find and fix.
Of course there are legal intentional uses of this instruction that sign extends the word
to a long word, or using it as a temporary register or for index purposes.
Also warns on `cmpa.w` with address register as second operand (there are some clever ways
using this, such as using `cmpa.w ax,ax` to see if a value is between $ffff8000 and $7fff).
Third instruction checked is `tst.w` on address registers for 68020+.
### Documentation provider
#### M68kSymbolDefinition
@@ -133,6 +144,7 @@ If the current statement has no valid syntax, the instruction details of all mat
- Switching the spaces option usually needs the caches to be invalidated. Find Usages word scanner always uses default settings, as it is not configurable per
project :-/
- Unit Test coverage is not as good as it could be (ahem).
- Code flow sometimes outputs "uhm?"
- Missing but planned features:
- Macro evaluation on invocation
- Folding
@@ -149,9 +161,10 @@ to highlight the same address and data registers while editing (see new `View ->
## Development notice
This plugin has been written in Kotlin 1.5 using Grammar-Kit.
This plugin has been written in Kotlin 1.9 using Grammar-Kit.
It is probably the only plugin (besides [Cajon](https://github.com/chrisly42/cajon-plugin) from the same author) that uses JUnit 5 Jupiter for unit testing so
It is probably the only plugin (besides [Cajon](https://git.platon42.de/chrisly42/cajon-plugin) from the same author) that uses JUnit 5 Jupiter for unit testing
so
far (or at least the only one I'm aware of ;) ). The IntelliJ framework actually uses the JUnit 3 TestCase for plugin testing, and it took me quite a while to
make it work with JUnit 5. Feel free to use the code (in package ```de.platon42.intellij.jupiter```) for your projects (with attribution).
@@ -164,6 +177,23 @@ are appreciated. It really is keeping me motivated to continue development.
## Changelog
### V0.11 (unreleased)
- New: Added M68kAddressRegisterWordAccessInspection to find suspicious uses of address registers.
### V0.10 (20-Feb-24)
- Decided to release some features that have been sitting on my harddrive for almost two years, but never got released,
because I was unable to get that damn formatter working in a way that was acceptable (the API and the docs are easily one
the worst abominations I've come across).
- Maintenance. Updated all dependencies to the latest versions.
- New: Added semantic highlighting. Currently available for data and address registers and local labels.
- Bugfix: addq/subq for address register stated it would affect the condition codes, which it in fact doesn't.
- New: Added simple custom navigation bar.
- New: Added folding support for functions and macro definitions.
- New: Added assembler directives to code completion (only lower-case except for other directives like IF
and MACRO, which are only suggested for upper-case).
### V0.9 (16-Aug-22)
- Maintenance. Updated all dependencies to the latest versions.
@@ -249,4 +279,4 @@ are appreciated. It really is keeping me motivated to continue development.
### V0.1 (20-Jul-21)
- Initial public release.
- Initial public release.
+20 -36
View File
@@ -1,68 +1,56 @@
plugins {
id 'java'
id 'org.jetbrains.intellij' version '1.9.0'
id 'org.jetbrains.kotlin.jvm' version '1.7.20'
id 'org.jetbrains.intellij' version '1.17.1'
id 'org.jetbrains.kotlin.jvm' version '1.9.22'
id 'jacoco'
id 'com.github.kt3k.coveralls' version '2.12.0'
}
group = 'de.platon42'
version = '0.10'
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
sourceCompatibility = 17
targetCompatibility = 17
repositories {
mavenCentral()
}
/*
To run tests in IntelliJ use these VM Options for run configuration
To run tests in IntelliJ use these VM Options for run configuration
-ea -Didea.system.path=build/idea-sandbox/system-test -Didea.config.path=build/idea-sandbox/config-test -Didea.plugins.path=build/idea-sandbox/plugins-test
*/
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
testImplementation 'org.assertj:assertj-core:3.23.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
testImplementation 'org.assertj:assertj-core:3.26.3'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.2'
testImplementation "org.jetbrains.kotlin:kotlin-test"
testImplementation "org.jetbrains.kotlin:kotlin-reflect"
testImplementation 'org.junit.platform:junit-platform-launcher:1.9.0'
testImplementation 'org.junit.platform:junit-platform-launcher:1.11.2'
// testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += "-Xjvm-default=all"
}
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
intellij {
setVersion("2022.2.3") // LATEST-EAP-SNAPSHOT
setVersion("2022.3") // LATEST-EAP-SNAPSHOT
setUpdateSinceUntilBuild(false)
// setPlugins(["com.intellij.java"])
}
runPluginVerifier {
ideVersions = ["IC-203.6682.168", "IC-222.4345.14", // 2020.3 - 2022.2.3
"CL-203.8084.11", // 2020.3
"CL-211.7628.27", // 2021.1
"CL-212.5712.21", // 2021.2
"CL-213.7172.20", // 2021.3.4
"CL-221.5921.22", // 2022.1.3
"CL-222.4345.21"] // 2022.2.4
ideVersions = ["IC-203.6682.168", "IC-233.14015.106", // 2020.3 - 2023.3
"CL-203.8084.11", "CL-233.14015.92"] // 2020.3 - 2023.3
downloadDir = System.getProperty("user.home") + "/.gradle/caches/modules-2/files-2.1/com.jetbrains.intellij.idea/verifier"
}
patchPluginXml {
setChangeNotes("""
<h4>V0.10 (undefined)</h4>
<h4>V0.10 (20-Feb-24)</h4>
<ul>
<li>Decided to release some features that have been sitting on my harddrive for almost two years, but never got released,
because I was unable to get that damn formatter working in a way that was acceptable (the API and the docs are easily one
the worst abominations I've come across).
<li>Maintenance. Updated all dependencies to the latest versions.
<li>New: Added semantic highlighting. Currently available for data and address registers and local labels.
<li>Bugfix: addq/subq for address register stated it would affect the condition codes, which it in fact doesn't.
<li>New: Added simple custom navigation bar.
@@ -70,7 +58,7 @@ patchPluginXml {
<li>New: Added assembler directives to code completion (only lower-case except for other directives like IF
and MACRO, which are only suggested for upper-case).
</ul>
<p>Full changelog available at <a href="https://github.com/chrisly42/mc68000-asm-plugin#changelog">Github project site</a>.</p>
<p>Full changelog available at <a href="https://git.platon42.de/chrisly42/mc68000-asm-plugin#changelog">Gitea project site</a>.</p>
""")
}
@@ -86,10 +74,6 @@ test {
}
}
tasks.coveralls {
dependsOn jacocoTestReport
}
jacoco {
toolVersion = '0.8.8'
}
@@ -103,4 +87,4 @@ jacocoTestReport {
publishPlugin {
setToken(intellijPublishToken)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
kotlin.code.style=official
kotlin.incremental=true
intellijPublishToken=perm:dummy
systemProp.jdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2"
systemProp.jdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2"
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+17
View File
@@ -0,0 +1,17 @@
Formatting:
- dc.w statements: Pressing return will automatically add dc.w in next line (plus tabs)
Showing of used registers in selection
Actions:
- Replace constant by symbol (with suggestion)
- Converting of hex values into decimal values and vice versa
- convert new style to old style
- convert old style to new style
- simplify explicit mnemonics (e.g. movea)
Marker:
- go to source register setter
- go to target register setter
- Can we add "find declaration to go for registers"
@@ -649,22 +649,12 @@ public class M68kParser implements PsiParser, LightPsiParser {
}
/* ********************************************************** */
// AsmInstruction | MacroCall
// AsmInstruction | MacroCall | PreprocessorDirective
static boolean Instruction(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "Instruction")) return false;
if (!nextTokenIs(b, "", MACRO_INVOCATION, MNEMONIC)) return false;
boolean r;
r = AsmInstruction(b, l + 1);
if (!r) r = MacroCall(b, l + 1);
return r;
}
/* ********************************************************** */
// Instruction|PreprocessorDirective
static boolean InstructionOnly(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "InstructionOnly")) return false;
boolean r;
r = Instruction(b, l + 1);
if (!r) r = PreprocessorDirective(b, l + 1);
return r;
}
@@ -681,13 +671,13 @@ public class M68kParser implements PsiParser, LightPsiParser {
}
/* ********************************************************** */
// LabelWithInstruction | LabelOnly | InstructionOnly
// LabelWithInstruction | LabelOnly | Instruction
static boolean LabelInsts(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "LabelInsts")) return false;
boolean r;
r = LabelWithInstruction(b, l + 1);
if (!r) r = LabelOnly(b, l + 1);
if (!r) r = InstructionOnly(b, l + 1);
if (!r) r = Instruction(b, l + 1);
return r;
}
@@ -698,27 +688,18 @@ public class M68kParser implements PsiParser, LightPsiParser {
}
/* ********************************************************** */
// Label (Instruction|PreprocessorDirective)
// Label Instruction
static boolean LabelWithInstruction(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "LabelWithInstruction")) return false;
if (!nextTokenIs(b, "", GLOBAL_LABEL_DEF, LOCAL_LABEL_DEF)) return false;
boolean r;
Marker m = enter_section_(b);
r = Label(b, l + 1);
r = r && LabelWithInstruction_1(b, l + 1);
r = r && Instruction(b, l + 1);
exit_section_(b, m, null, r);
return r;
}
// Instruction|PreprocessorDirective
private static boolean LabelWithInstruction_1(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "LabelWithInstruction_1")) return false;
boolean r;
r = Instruction(b, l + 1);
if (!r) r = PreprocessorDirective(b, l + 1);
return r;
}
/* ********************************************************** */
// LOCAL_LABEL_DEF COLON?
public static boolean LocalLabel(PsiBuilder b, int l) {
@@ -10,7 +10,7 @@ class M68kFileElementType private constructor() : ILightStubFileElementType<PsiF
@JvmField
val INSTANCE = M68kFileElementType()
const val STUB_VERSION = 7
const val STUB_VERSION = 8
const val STUB_EXTERNAL_ID_PREFIX = "MC68000."
const val EXTERNAL_ID = STUB_EXTERNAL_ID_PREFIX + "FILE"
}
@@ -19,7 +19,9 @@ object AssemblerDirectives {
"align", "even", "odd", "cnop", "long", "dphrase", "phrase", "qphrase",
"cargs", "comm", "comment",
"rsset", "clrfo", "clrso", "setfo", "setso"
"rsset", "clrfo", "clrso", "setfo", "setso",
"rsreset", "rs.b", "rs.w", "rs.l"
)
val plainDirectives: Set<String> = setOf(
@@ -88,13 +88,13 @@ fun getCcInfo(cc: Int): Map<String, Pair<String, String>> {
CC_V_OR -> "*" to CC_OR_STR
else -> "*" to "$CC_RES_STR (usually for overflows)"
}
xnzvcMap["C"] = when (cc and CC_V_TST) {
xnzvcMap["C"] = when (cc and CC_C_TST) {
0 -> "-" to CC_NOT_AFFECTED_STR
CC_V_SET -> "1" to CC_ALWAYS_SET_STR
CC_V_CLEAR -> "0" to CC_ALWAYS_CLEAR_STR
CC_V_UNDEF -> "U" to CC_UNDEFINED_STR
CC_V_AND -> "*" to CC_AND_STR
CC_V_OR -> "*" to CC_OR_STR
CC_C_SET -> "1" to CC_ALWAYS_SET_STR
CC_C_CLEAR -> "0" to CC_ALWAYS_CLEAR_STR
CC_C_UNDEF -> "U" to CC_UNDEFINED_STR
CC_C_AND -> "*" to CC_AND_STR
CC_C_OR -> "*" to CC_OR_STR
else -> "*" to "$CC_RES_STR (usually carry/borrow)"
}
@@ -1085,7 +1085,7 @@ object M68kIsa {
IsaData(
"stop", "Stop",
machine = ALL_MACHINES, isPrivileged = true,
modes = listOf(AllowedAdrMode(setOf(AddressMode.IMMEDIATE_DATA), null, OP_UNSIZED))
modes = listOf(AllowedAdrMode(setOf(AddressMode.IMMEDIATE_DATA), null, OP_UNSIZED, affectedCc = cc("*****")))
),
*autoExpandForOtherCpus(
@@ -1208,10 +1208,10 @@ object M68kIsa {
private val mnemonicLookupMap = isaData.asSequence()
.flatMap {
(if (it.conditionCodes.isEmpty()) it.altMnemonics.plus(it.mnemonic) else it.altMnemonics.plus(it.conditionCodes
.map { cc ->
it.mnemonic.replace("CC", cc)
})).map { mnemonic -> mnemonic to it }
(if (it.conditionCodes.isEmpty()) it.altMnemonics.plus(it.mnemonic) else it.altMnemonics.plus(
it.conditionCodes
.map { cc -> it.mnemonic.replace("CC", cc) }))
.map { mnemonic -> mnemonic to it }
}
.groupBy({ it.first }) { it.second }
@@ -1223,24 +1223,16 @@ object M68kIsa {
fun findMatchingOpMode(candidates: List<IsaData>, op1: AddressMode?, op2: AddressMode?, opSize: Int?, specialReg: String?): List<IsaData> {
return candidates.filter {
it.modes.any { am ->
isAddressModeMatching(am, op1, op2, specialReg)
&& ((opSize == null) || ((opSize and am.size) == opSize))
}
it.modes.any { am -> isAddressModeMatching(am, op1, op2, specialReg) && ((opSize == null) || ((opSize and am.size) == opSize)) }
}
}
fun findMatchingAddressMode(modes: List<AllowedAdrMode>, op1: AddressMode?, op2: AddressMode?, opSize: Int?, specialReg: String?): List<AllowedAdrMode> {
return modes.filter { am ->
isAddressModeMatching(am, op1, op2, specialReg)
&& ((opSize == null) || ((opSize and am.size) == opSize))
}
return modes.filter { am -> isAddressModeMatching(am, op1, op2, specialReg) && ((opSize == null) || ((opSize and am.size) == opSize)) }
}
fun findMatchingOpModeIgnoringSize(candidates: List<IsaData>, op1: AddressMode?, op2: AddressMode?, specialReg: String?): List<IsaData> {
return candidates.filter {
it.modes.any { am -> isAddressModeMatching(am, op1, op2, specialReg) }
}
return candidates.filter { it.modes.any { am -> isAddressModeMatching(am, op1, op2, specialReg) } }
}
fun findSupportedOpSizes(candidates: List<IsaData>, op1: AddressMode?, op2: AddressMode?, specialReg: String?): Int {
@@ -1253,6 +1245,6 @@ object M68kIsa {
private fun isAddressModeMatching(am: AllowedAdrMode, op1: AddressMode?, op2: AddressMode?, specialReg: String?) =
((((op1 == null) && (am.op1 == null)) || am.op1?.contains(op1) ?: false)
&& (((op2 == null) && (am.op2 == null)) || am.op2?.contains(op2) ?: false)
&& ((specialReg == null) || (am.specialReg?.split('|')?.any { it.equals(specialReg, true) } ?: false)))
}
&& (((op2 == null) && (am.op2 == null)) || am.op2?.contains(op2) ?: false)
&& ((specialReg == null) || (am.specialReg?.split('|')?.any { it.equals(specialReg, true) } ?: false)))
}
@@ -0,0 +1,23 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.formatting.Alignment
import com.intellij.formatting.Block
import com.intellij.formatting.Spacing
import com.intellij.formatting.Wrap
import com.intellij.lang.ASTNode
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.formatter.common.AbstractBlock
class M68kAsmAssignmentBlock(
node: ASTNode, wrap: Wrap?, alignment: Alignment?,
private val codeStyleSettings: CodeStyleSettings
) : AbstractBlock(node, wrap, alignment) {
override fun getSpacing(child1: Block?, child2: Block): Spacing? {
return Spacing.getReadOnlySpacing()
}
override fun isLeaf() = true
override fun buildChildren() = emptyList<Block>()
}
@@ -0,0 +1,62 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.formatting.Alignment
import com.intellij.formatting.Block
import com.intellij.formatting.Spacing
import com.intellij.formatting.Wrap
import com.intellij.lang.ASTNode
import com.intellij.psi.TokenType
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.formatter.common.AbstractBlock
import de.platon42.intellij.plugins.m68k.psi.M68kTypes
class M68kAsmBlock(
node: ASTNode, wrap: Wrap?, alignment: Alignment?,
val column: Int,
private val codeStyleSettings: CodeStyleSettings
) : AbstractBlock(node, wrap, alignment) {
override fun getSpacing(child1: Block?, child2: Block): Spacing? {
if (child1 is M68kAsmBlock && child2 is M68kAsmBlock) {
val columnDiff = child2.column - child1.column
if (columnDiff > 0) {
var minSpaces = columnDiff * codeStyleSettings.indentOptions.INDENT_SIZE - child1.node.textLength
while (minSpaces < 1) {
minSpaces += codeStyleSettings.indentOptions.INDENT_SIZE
}
return Spacing.createSpacing(minSpaces, minSpaces, 0, false, 0)
}
}
return null
}
override fun isLeaf() = (node.firstChildNode == null)
override fun buildChildren(): List<Block> {
val subBlocks = ArrayList<Block>()
if (myNode.elementType == M68kTypes.ASM_OP) {
return subBlocks
}
var child = myNode.firstChildNode
var newColumn = column
while (child != null) {
if (child.elementType != TokenType.WHITE_SPACE) {
subBlocks.add(
M68kAsmBlock(
child,
null,
null,
newColumn,
codeStyleSettings
)
)
if (child.elementType == M68kTypes.ASM_OP) {
newColumn += 2
}
}
child = child.treeNext
}
return subBlocks
}
}
@@ -0,0 +1,7 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.codeStyle.CustomCodeStyleSettings
class M68kAsmCodeStyleSettings(container: CodeStyleSettings) : CustomCodeStyleSettings("MC68000AsmCodeStyleSettings", container) {
}
@@ -0,0 +1,31 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.application.options.CodeStyleAbstractConfigurable
import com.intellij.application.options.CodeStyleAbstractPanel
import com.intellij.application.options.TabbedLanguageCodeStylePanel
import com.intellij.psi.codeStyle.CodeStyleConfigurable
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.codeStyle.CodeStyleSettingsProvider
import com.intellij.psi.codeStyle.CustomCodeStyleSettings
import de.platon42.intellij.plugins.m68k.MC68000Language
class M68kAsmCodeStyleSettingsProvider : CodeStyleSettingsProvider() {
override fun createCustomSettings(settings: CodeStyleSettings): CustomCodeStyleSettings {
return M68kAsmCodeStyleSettings(settings)
}
override fun createConfigurable(settings: CodeStyleSettings, modelSettings: CodeStyleSettings): CodeStyleConfigurable {
return object : CodeStyleAbstractConfigurable(settings, modelSettings, configurableDisplayName) {
override fun createPanel(settings: CodeStyleSettings): CodeStyleAbstractPanel {
return M68kAsmCodeStyleMainPanel(currentSettings, settings)
}
}
}
override fun getConfigurableDisplayName() = "M68k"
private class M68kAsmCodeStyleMainPanel(currentSettings: CodeStyleSettings, settings: CodeStyleSettings) :
TabbedLanguageCodeStylePanel(MC68000Language.INSTANCE, currentSettings, settings)
}
@@ -0,0 +1,21 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.formatting.Block
import com.intellij.formatting.Spacing
import com.intellij.lang.ASTNode
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.formatter.common.AbstractBlock
class M68kAsmCommentBlock(
node: ASTNode,
private val codeStyleSettings: CodeStyleSettings
) : AbstractBlock(node, null, null) {
override fun getSpacing(child1: Block?, child2: Block): Spacing? {
return Spacing.getReadOnlySpacing()
}
override fun isLeaf() = true
override fun buildChildren() = emptyList<Block>()
}
@@ -0,0 +1,14 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.formatting.Block
import com.intellij.lang.ASTNode
import com.intellij.psi.formatter.common.AbstractBlock
class M68kAsmEolBlock(node: ASTNode) : AbstractBlock(node, null, null) {
override fun getSpacing(child1: Block?, child2: Block) = null
override fun isLeaf() = true
override fun buildChildren() = emptyList<Block>()
}
@@ -0,0 +1,19 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.formatting.FormattingContext
import com.intellij.formatting.FormattingModel
import com.intellij.formatting.FormattingModelBuilder
import com.intellij.formatting.FormattingModelProvider
class M68kAsmFormattingModelBuilder : FormattingModelBuilder {
override fun createModel(formattingContext: FormattingContext): FormattingModel {
val codeStyleSettings = formattingContext.codeStyleSettings
return FormattingModelProvider.createFormattingModelForPsiFile(
formattingContext.containingFile,
M68kAsmRootBlock(formattingContext.node, codeStyleSettings),
codeStyleSettings
)
}
}
@@ -0,0 +1,23 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.formatting.Alignment
import com.intellij.formatting.Block
import com.intellij.formatting.Spacing
import com.intellij.formatting.Wrap
import com.intellij.lang.ASTNode
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.formatter.common.AbstractBlock
class M68kAsmLabelBlock(
node: ASTNode, wrap: Wrap?, alignment: Alignment?,
private val codeStyleSettings: CodeStyleSettings
) : AbstractBlock(node, wrap, alignment) {
override fun getSpacing(child1: Block?, child2: Block): Spacing? {
return Spacing.getReadOnlySpacing()
}
override fun isLeaf() = true
override fun buildChildren() = emptyList<Block>()
}
@@ -0,0 +1,21 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.formatting.Block
import com.intellij.formatting.Spacing
import com.intellij.lang.ASTNode
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.formatter.common.AbstractBlock
class M68kAsmMacroDefBlock(
node: ASTNode,
private val codeStyleSettings: CodeStyleSettings
) : AbstractBlock(node, null, null) {
override fun getSpacing(child1: Block?, child2: Block): Spacing? {
return Spacing.getReadOnlySpacing()
}
override fun isLeaf() = true
override fun buildChildren() = emptyList<Block>()
}
@@ -0,0 +1,70 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.formatting.Block
import com.intellij.formatting.Indent
import com.intellij.formatting.Spacing
import com.intellij.lang.ASTNode
import com.intellij.psi.PsiComment
import com.intellij.psi.TokenType
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.formatter.common.AbstractBlock
import de.platon42.intellij.plugins.m68k.psi.M68kMacroDefinition
import de.platon42.intellij.plugins.m68k.psi.M68kStatement
import de.platon42.intellij.plugins.m68k.psi.M68kTypes
class M68kAsmRootBlock(node: ASTNode, private val codeStyleSettings: CodeStyleSettings) : AbstractBlock(node, null, null) {
override fun getIndent(): Indent? {
return Indent.getAbsoluteNoneIndent()
}
override fun getSpacing(child1: Block?, child2: Block): Spacing? {
if (child2 is M68kAsmEolBlock) {
return Spacing.createSpacing(0, 0, 0, false, 0)
//return null
}
if (child2 is M68kAsmMacroDefBlock) {
return child2.getSpacing(child1, child2)
}
if (child2 is M68kAsmCommentBlock) {
if (child1 is M68kAsmStatementBlock) {
val indentSize = codeStyleSettings.indentOptions.INDENT_SIZE
val oddIdent = indentSize - (child1.node.textLength % indentSize)
return Spacing.createSpacing(oddIdent, Integer.MAX_VALUE, 0, false, 0)
} else {
return Spacing.getReadOnlySpacing()
}
}
if (child2.subBlocks.isNotEmpty()) {
return child2.getSpacing(child1, child2.subBlocks.first())
}
return null
}
override fun isLeaf(): Boolean {
return false
}
override fun buildChildren(): List<Block> {
val subBlocks = ArrayList<Block>()
var child = myNode.firstChildNode
while (child != null) {
if (child.elementType == M68kTypes.EOL) {
subBlocks.add(M68kAsmEolBlock(child))
} else if (child.elementType != TokenType.WHITE_SPACE) {
if (child.psi is M68kStatement) {
subBlocks.add(M68kAsmStatementBlock(child, codeStyleSettings))
} else if (child.psi is M68kMacroDefinition) {
subBlocks.add(M68kAsmMacroDefBlock(child, codeStyleSettings))
} else if (child.psi is PsiComment) {
subBlocks.add(M68kAsmCommentBlock(child, codeStyleSettings))
} else {
subBlocks.add(M68kAsmBlock(child, null, null, 0, codeStyleSettings))
}
}
child = child.treeNext
}
return subBlocks
}
}
@@ -0,0 +1,74 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.formatting.Block
import com.intellij.formatting.Indent
import com.intellij.formatting.Spacing
import com.intellij.lang.ASTNode
import com.intellij.psi.TokenType
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.formatter.common.AbstractBlock
import de.platon42.intellij.plugins.m68k.psi.*
class M68kAsmStatementBlock(node: ASTNode, private val codeStyleSettings: CodeStyleSettings) :
AbstractBlock(node, null, null) {
override fun getIndent(): Indent? {
val statement = myNode.psi as M68kStatement
if (((statement.asmInstruction != null) || (statement.asmInstruction != null) || (statement.preprocessorDirective != null)
|| (statement.macroCall != null))
&& ((statement.localLabel == null) && (statement.globalLabel == null))
) {
return null
} else {
return Indent.getAbsoluteNoneIndent()
}
}
override fun getSpacing(child1: Block?, child2: Block): Spacing? {
if (child2 is M68kAsmBlock) {
val indentSize = codeStyleSettings.indentOptions.INDENT_SIZE * child2.column
if (child1 is M68kAsmEolBlock || child1 is M68kAsmCommentBlock) {
return Spacing.createSpacing(indentSize, indentSize, 0, true, 0)
}
if (child1 is M68kAsmLabelBlock) {
val spacesLeft = indentSize - child1.node.textLength
if (spacesLeft <= 0) {
return Spacing.createSpacing(0, 0, 1, true, 0)
} else {
return Spacing.createSpacing(spacesLeft, spacesLeft, 0, true, 0)
}
}
return Spacing.createSpacing(indentSize, indentSize, 0, true, 0)
}
if (child2 is M68kAsmLabelBlock || child2 is M68kAsmAssignmentBlock) {
return Spacing.createSpacing(0, 0, 0, true, 0)
}
return null
}
override fun isLeaf() = false
override fun buildChildren(): List<Block> {
val subBlocks = ArrayList<Block>()
var child = myNode.firstChildNode
while (child != null) {
if (child.elementType != TokenType.WHITE_SPACE) {
val element = child.psi
when (element) {
is M68kAssignment -> subBlocks.add(M68kAsmAssignmentBlock(child, null, null, codeStyleSettings))
is M68kAsmInstruction, is M68kPreprocessorDirective, is M68kMacroCall ->
subBlocks.add(M68kAsmBlock(child, null, null, 2, codeStyleSettings))
is M68kGlobalLabel, is M68kLocalLabel ->
subBlocks.add(M68kAsmLabelBlock(child, null, null, codeStyleSettings))
else -> subBlocks.add(M68kAsmBlock(child, null, null, 1, codeStyleSettings))
}
}
child = child.treeNext
}
return subBlocks
}
}
@@ -0,0 +1,41 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider
import de.platon42.intellij.plugins.m68k.MC68000Language
class M68kLanguageCodeStyleSettingsProvider : LanguageCodeStyleSettingsProvider() {
override fun getLanguage() = MC68000Language.INSTANCE
override fun getCodeSample(settingsType: SettingsType) = """; This is an example assembly language program
PIC_HEIGHT = 256
include "../includes/hardware/custom.i"
BLTHOGON MACRO ; macro definition
move.w #DMAF_SETCLR|DMAF_BLITHOG,dmacon(a5) ; hog!
ENDM
demo_init ; global label
tst.w d1
beq.s .skip
PUSHM d0-d7/a0-a6 ; this is a macro call
lea hello(pc),a1
lea pd_ModViewTable(a4,d1.w),a0
moveq.l #0,d0
move.w #PIC_HEIGHT-1,d7
.loop move.l d0,(a0)+ ; local label
dbra d7,.loop
POPM
.skip rts
irq: move.l a0,-(sp)
move usp,a0
move.l a0,${'$'}400.w
move.l (sp)+,a0
rte
hello: dc.b 'Hello World!',10,0
even
dc.w *-hello ; length of string
"""
}
@@ -0,0 +1,69 @@
package de.platon42.intellij.plugins.m68k.inspections
import com.intellij.codeInspection.InspectionManager
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import de.platon42.intellij.plugins.m68k.asm.AddressMode
import de.platon42.intellij.plugins.m68k.asm.M68kIsa.findMatchingInstructions
import de.platon42.intellij.plugins.m68k.asm.OP_SIZE_W
import de.platon42.intellij.plugins.m68k.psi.M68kAddressModeUtil
import de.platon42.intellij.plugins.m68k.psi.M68kAsmInstruction
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.findExactIsaDataAndAllowedAdrModeForInstruction
class M68kAddressRegisterWordAccessInspection : AbstractBaseM68kLocalInspectionTool() {
companion object {
private const val DISPLAY_NAME = "Word access to address register"
private const val ADDR_REG_WORD_ACCESS_MSG_TEMPLATE = "Suspicious %s %s address register"
}
override fun getDisplayName() = DISPLAY_NAME
override fun checkAsmInstruction(asmInstruction: M68kAsmInstruction, manager: InspectionManager, isOnTheFly: Boolean): Array<ProblemDescriptor>? {
val asmOp = asmInstruction.asmOp
if (asmOp.opSize != OP_SIZE_W) return emptyArray()
val isaDataCandidates = findMatchingInstructions(asmOp.mnemonic)
if (isaDataCandidates.isEmpty()) return emptyArray()
val (isaData, adrMode) = findExactIsaDataAndAllowedAdrModeForInstruction(asmInstruction) ?: return emptyArray()
return when (isaData.mnemonic) {
"movea" -> arrayOf(
manager.createProblemDescriptor(
asmInstruction,
asmInstruction,
ADDR_REG_WORD_ACCESS_MSG_TEMPLATE.format(isaData.mnemonic, "word write to"),
ProblemHighlightType.WARNING,
isOnTheFly
)
)
"cmpa" -> arrayOf(
manager.createProblemDescriptor(
asmInstruction,
asmInstruction,
ADDR_REG_WORD_ACCESS_MSG_TEMPLATE.format(isaData.mnemonic, "comparing with word-sized"),
ProblemHighlightType.WARNING,
isOnTheFly
)
)
"tst" -> if (M68kAddressModeUtil.getAddressModeForType(asmInstruction.addressingModeList.getOrNull(0)) == AddressMode.ADDRESS_REGISTER_DIRECT) {
arrayOf(
manager.createProblemDescriptor(
asmInstruction,
asmInstruction,
ADDR_REG_WORD_ACCESS_MSG_TEMPLATE.format(isaData.mnemonic, "with word-sized"),
ProblemHighlightType.WARNING,
isOnTheFly
)
)
} else {
emptyArray()
}
else -> emptyArray()
}
}
}
@@ -136,11 +136,10 @@ SymbolDefinition ::= SYMBOLDEF {
Assignment ::= SymbolDefinition COLON? (OP_ASSIGN|EQU) expr
private LabelInsts ::= LabelWithInstruction | LabelOnly | InstructionOnly
private LabelInsts ::= LabelWithInstruction | LabelOnly | Instruction
private LabelOnly ::= Label
private LabelWithInstruction ::= Label (Instruction|PreprocessorDirective)
private InstructionOnly ::= (Instruction|PreprocessorDirective)
private LabelWithInstruction ::= Label Instruction
LocalLabel ::= LOCAL_LABEL_DEF COLON? {
name = "local label"
@@ -199,7 +198,7 @@ MacroCall ::= MACRO_INVOCATION PlainOperands? {
}
AsmInstruction ::= AsmOp AsmOperands?
private Instruction ::= AsmInstruction | MacroCall
private Instruction ::= AsmInstruction | MacroCall | PreprocessorDirective
//external Instruction ::= parseMacroCallOrAsmInstruction
private AsmOperands ::= AddressingMode (SEPARATOR AddressingMode)?
@@ -12,22 +12,26 @@ import de.platon42.intellij.plugins.m68k.psi.utils.M68kLookupUtil
class M68kGlobalLabelSymbolCompletionContributor : CompletionContributor() {
companion object {
val REGS = listOf(
"d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
"a0", "a1", "a2", "a3", "a4", "a5", "a6", "sp",
"pc"
)
val REGSET = REGS.toSet()
val REGISTER_SUGGESTIONS: List<LookupElement> =
listOf(
"d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
"a0", "a1", "a2", "a3", "a4", "a5", "a6", "sp",
"pc"
)
.map { PrioritizedLookupElement.withPriority(LookupElementBuilder.create(it).withIcon(M68kIcons.REGISTER).withBoldness(true), 2.0) }
REGS.map { PrioritizedLookupElement.withPriority(LookupElementBuilder.create(it).withIcon(M68kIcons.REGISTER).withBoldness(true), 2.0) }
}
init {
extend(CompletionType.BASIC, PlatformPatterns.psiElement(M68kTypes.SYMBOL), object : CompletionProvider<CompletionParameters>() {
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, resultSet: CompletionResultSet) {
resultSet.addAllElements(REGISTER_SUGGESTIONS)
resultSet.addAllElements(M68kLookupUtil.findAllGlobalLabels(parameters.originalFile.project).map(LookupElementBuilder::createWithIcon))
resultSet.addAllElements(M68kLookupUtil.findAllSymbolDefinitions(parameters.originalFile.project).map(LookupElementBuilder::createWithIcon))
val prefix = resultSet.prefixMatcher.prefix.lowercase()
if (!(prefix == "a" || prefix == "d" || REGSET.contains(prefix))) {
resultSet.addAllElements(M68kLookupUtil.findAllGlobalLabels(parameters.originalFile.project).map(LookupElementBuilder::createWithIcon))
resultSet.addAllElements(M68kLookupUtil.findAllSymbolDefinitions(parameters.originalFile.project).map(LookupElementBuilder::createWithIcon))
}
}
})
}
}
}
+12 -6
View File
@@ -1,14 +1,14 @@
<idea-plugin>
<id>de.platon42.m68k</id>
<name>MC68000 Assembly Language Support</name>
<vendor email="chrisly@platon42.de" url="https://github.com/chrisly42/mc68000-asm-plugin">Chris 'platon42' Hodges
<vendor email="chrisly@platon42.de" url="https://git.platon42.de/chrisly42/mc68000-asm-plugin">Chris 'platon42' Hodges
</vendor>
<description><![CDATA[
MC68000 Assembly Language Plugin adds language support for Motorola 68000 (M68k) assembly language files (asm).
It provides syntax highlighting and refactoring possibilities among other things.
<p>
<a href="https://github.com/chrisly42/mc68000-asm-plugin/blob/main/README.md">Full documentation here...</a>
<a href="https://git.platon42.de/chrisly42/mc68000-asm-plugin/blob/main/README.md">Full documentation here...</a>
]]></description>
<idea-version since-build="203.5981.166"/>
@@ -21,9 +21,12 @@
implementationClass="de.platon42.intellij.plugins.m68k.parser.M68kParserDefinition"/>
<lang.syntaxHighlighterFactory language="MC68000"
implementationClass="de.platon42.intellij.plugins.m68k.syntax.M68kSyntaxHighlighterFactory"/>
<lang.foldingBuilder
language="MC68000"
implementationClass="de.platon42.intellij.plugins.m68k.folding.M68kFoldingBuilder"/>
<lang.foldingBuilder language="MC68000"
implementationClass="de.platon42.intellij.plugins.m68k.folding.M68kFoldingBuilder"/>
<!--lang.formatter language="MC68000"
implementationClass="de.platon42.intellij.plugins.m68k.formatter.M68kAsmFormattingModelBuilder"/-->
<codeStyleSettingsProvider implementation="de.platon42.intellij.plugins.m68k.formatter.M68kAsmCodeStyleSettingsProvider"/>
<langCodeStyleSettingsProvider implementation="de.platon42.intellij.plugins.m68k.formatter.M68kLanguageCodeStyleSettingsProvider"/>
<navbar implementation="de.platon42.intellij.plugins.m68k.navigation.M68kStructureAwareNavbar"/>
<highlightVisitor implementation="de.platon42.intellij.plugins.m68k.syntax.M68kRainbowVisitor"/>
<colorSettingsPage implementation="de.platon42.intellij.plugins.m68k.syntax.M68kColorSettingsPage"/>
@@ -74,6 +77,9 @@
<localInspection implementationClass="de.platon42.intellij.plugins.m68k.inspections.M68kUnresolvedReferenceInspection"
displayName="Unresolved label/symbol/macro reference" groupName="M68k"
enabledByDefault="true" level="WARNING"/>
<localInspection implementationClass="de.platon42.intellij.plugins.m68k.inspections.M68kAddressRegisterWordAccessInspection"
displayName="Suspicious word access to address register" groupName="M68k"
enabledByDefault="true" level="WARNING"/>
<projectService serviceImplementation="de.platon42.intellij.plugins.m68k.settings.M68kProjectSettings"/>
@@ -84,4 +90,4 @@
<actions>
</actions>
</idea-plugin>
</idea-plugin>
@@ -0,0 +1,52 @@
package de.platon42.intellij.plugins.m68k.formatter
import com.intellij.application.options.CodeStyle
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import com.intellij.util.containers.ContainerUtil
import de.platon42.intellij.jupiter.LightCodeInsightExtension
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataPath
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@TestDataPath("src/test/resources/formatting")
@ExtendWith(LightCodeInsightExtension::class)
internal class M68kAsmFormatterTest {
@Test
@Disabled
internal fun check_if_formatting_works_as_expected(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.configureByFile("basic_before.asm")
val settings = CodeStyle.getLanguageSettings(myFixture.file)
settings.indentOptions?.INDENT_SIZE = 6
settings.indentOptions?.CONTINUATION_INDENT_SIZE = 9
WriteCommandAction.runWriteCommandAction(myFixture.project)
{
CodeStyleManager.getInstance(myFixture.project).reformatText(
myFixture.file,
ContainerUtil.newArrayList(myFixture.file.textRange)
)
}
myFixture.checkResultByFile("basic_after.asm")
}
@Test
internal fun check_formatting_for_mnemonics(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.configureByFile("mnemonics_before.asm")
val settings = CodeStyle.getLanguageSettings(myFixture.file)
settings.indentOptions?.INDENT_SIZE = 8
WriteCommandAction.runWriteCommandAction(myFixture.project)
{
CodeStyleManager.getInstance(myFixture.project).reformatText(
myFixture.file,
ContainerUtil.newArrayList(myFixture.file.textRange)
)
}
myFixture.checkResultByFile("mnemonics_after.asm")
}
}
@@ -0,0 +1,29 @@
package de.platon42.intellij.plugins.m68k.inspections
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import org.junit.jupiter.api.Test
internal class M68kAddressRegisterWordAccessInspectionTest : AbstractInspectionTest() {
@Test
internal fun shows_warning_on_movea_word_sized(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kAddressRegisterWordAccessInspection::class.java)
myFixture.configureByText("wordaccess.asm", " move.w d0,a0")
assertHighlightings(myFixture, 1, "Suspicious movea word write to address register")
}
@Test
internal fun shows_warning_on_cmpa_word_sized(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kAddressRegisterWordAccessInspection::class.java)
myFixture.configureByText("wordaccess.asm", " cmp.w d0,a0")
assertHighlightings(myFixture, 1, "Suspicious cmpa comparing with word-sized address register")
}
@Test
internal fun shows_warning_on_tst_with_word_sized_address_register(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kAddressRegisterWordAccessInspection::class.java)
myFixture.configureByText("wordaccess.asm", " tst.w a0")
assertHighlightings(myFixture, 1, "Suspicious tst with word-sized address register")
}
}
@@ -0,0 +1,51 @@
; this is a test
FOO = 1
; this is the main demo routine
demo_main:
moveq.l #0,d0 ; end of line comment
rts ; some more comments
; data area starts here
.data dc.w 10,0
even
this_is_a_very_long_label: moveq.l #0,d0 ; and an end of line comment, too
; this is another folding area
; could be anything.
intro_part1
; What is this doing here?
dc.w $47fe
illegal
rts
; this comment is two lines away
intro_part2:
moveq.l #0,d1
rts
; standard macros
BLTWAIT MACRO
.bw\@
btst #DMAB_BLTDONE-8,dmaconr(a5)
bne.s .bw\@
ENDM
MACRO BLTHOGOFF
moveq.l #0,d0
ENDM
********************** foobar
some_more_data:
dc.w $123
dc.w $345
dc.w $333
dc.w $222
CUBE_SIZE = 100
@@ -0,0 +1,51 @@
; this is a test
FOO = 1
; this is the main demo routine
demo_main:
moveq.l #0,d0 ; end of line comment
rts ; some more comments
; data area starts here
.data dc.w 10,0
even
this_is_a_very_long_label: moveq.l #0,d0 ; and an end of line comment, too
; this is another folding area
; could be anything.
intro_part1
; What is this doing here?
dc.w $47fe
illegal
rts
; this comment is two lines away
intro_part2:
moveq.l #0,d1
rts
; standard macros
BLTWAIT MACRO
.bw\@
btst #DMAB_BLTDONE-8,dmaconr(a5)
bne.s .bw\@
ENDM
MACRO BLTHOGOFF
moveq.l #0,d0
ENDM
********************** foobar
some_more_data:
dc.w $123
dc.w $345
dc.w $333
dc.w $222
CUBE_SIZE = 100
@@ -0,0 +1,32 @@
FOO = 1234
BAR = 1235
COUNTER SET COUNTER+1
FISH MACRO
.fish\@ rts
ENDM
MACRO SOUP
moveq.l #0,d0
ENDM
; with space
; standalone comment
moveq.l #10,d1 ; end of line comment
FISH
add.l #10,d1
SOUP
subq.b #2,d2 ; end of line comment
bra.s .foo
nop
.foo move.l d2,d1
rts
.verylonglabel:
stop #$2000
.narf
moveq.l #2,d0
globallabel:
moveq.l #0,d0
@@ -0,0 +1,31 @@
FOO = 1234
BAR = 1235
COUNTER SET COUNTER+1
FISH MACRO
.fish\@ rts
ENDM
MACRO SOUP
moveq.l #0,d0
ENDM
; with space
; standalone comment
moveq.l #10,d1; end of line comment
FISH
add.l #10,d1
SOUP
subq.b #2,d2 ; end of line comment
bra.s .foo
nop
.foo move.l d2,d1
rts
.verylonglabel: stop #$2000
.narf
moveq.l #2,d0
globallabel: moveq.l #0,d0