Git Product home page Git Product logo

m68k-lsp's Introduction

Motorola 68000 family language server

example workflow npm version

Language Server Protocol implementation for Motorola 68000 family assembly, based on tree-sitter-m68k

Features

  • Auto-completion:
    • Instruction mnemonics
    • Assembler directives
    • Registers
    • Symbols
  • Code Linting
    • Parser errors
    • Processor support
  • Code Folding
  • Document Formatting
  • Document Highlights
  • Document Links
  • Document Symbols
  • Find References
  • Go to definition
  • Hover
    • Instruction/directive documentation
    • Symbol info
  • Multiple workspaces
  • Rename Symbols
  • Signature Help

Installation

Install the package via npm:

npm install --global m68k-lsp-server

Usage

Neovim

Configure using nvim-lspconfig

e.g.

require('lspconfig').m68k.setup{
  on_attach = on_attach,
  init_options = {
    includePaths = { '../include', '/home/myuser/includes' },
    format = {
      case = {
        instruction = 'upper'
      }
    }
  }
}

Emacs

See emacs-m68k

Standalone server

Start the server e.g.:

m68k-lsp-server --stdio

Configuration

The LSP client can configured using the following settings, either as initialization options, or as a .m68krc.json file in your workspace root, for project specific overrides.

Processors:

Lists the processor(s) that your code is targeted at. This controls completion suggestions and provides diagnostics.

{
  "processors": ["mc68030", "mc68881"]
}

Default: ["mc68000"]

Supported values: mc68000 ,mc68010 ,mc68020 ,mc68030 ,mc68040 ,mc68060 ,mc68881 ,mc68851 ,cpu32

Include Paths:

Additional paths to use to resolve include directives. This is equivalent to INCDIR in source. It should probably include anything you pass to vasm -I arguments. Can be absolute or relative.

{
  "includePaths": ["../include", "/home/myuser/includes"]
}

Default: []

vasm diagnostics:

The server can use vasm to provide diagnostic messages. When enabled it will assemble source files on save/open and display any errors or warnings.

The server will use a local vasmm68k_mot executable if one exists in your path or is configured in vasm.binPath, otherwise it will default to a bundled version complied in Web Assembly.

{
  "vasm": {
    "provideDiagnostics": true,
    "binPath": "vasmm68k_mot",
    "args": [],
    "preferWasm": false,
    "exclude": []
  }
}

(defaults)

Property Description
provideDiagnostics Enable vasm diagnostics
binPath Filename or full path of vasm executable binary
args Custom arguments to pass to vasm. Include paths and processor(s) from server config will automatically be added
preferWasm Always use bundled Web Assembly vasm
exclude File patterns to ignore and not build directly e.g. ["*.i"]

Formatting:

The language server supports document formatting which can be configured using the following options:

Case

Enforce consistency of upper/lower case on elements which are normally case insensitive.

Option Behaviour
upper Upper case
lower Lower case
any Do not change case

This can either be configured globally for all elements:

{
  "format": {
    "case": "lower"
  }
}

or per element type

{
  "format": {
    "case": {
      "instruction": "lower",
      "directive": "lower",
      "control": "upper",
      "sectionType": "lower",
      "register": "lower",
      "hex": "lower"
    }
  }
}
Element Description
instruction Instruction mnemonic/size e.g. move.w
directive Assembler directive mnemonic/qualifier e.g. include
control Assembler control keywords e.g. ifeq/endc
sectionType Section type e.g. bss
register Register name e.g. d0,sr
hex Hexadecimal number literal

Default: "lower"

Label colon

Determines whether labels should have a colon suffix.

Can be set for all labels:

{
  "format": {
    "labelColon": "on"
  }
}

or individually for global and local labels:

{
  "format": {
    "labelColon": {
      "global": "on",
      "local": "off"
    }
  }
}
Option Behaviour
on Add colon
off Remove colon
notInline No colon for labels on same line as instruction
onlyInline Only add colon for labels on same line as instruction
any Do not change

Default: "on"

Operand space

Include space between operands e.g. move d0, d1. VASM needs -spaces or -phxass option to support this.

Option Behaviour
on Add space
off Remove space
any Do not change

Default: "off"

{
  "format": {
    "operandSpace": "off"
  }
}

Quotes

Quote style to use for strings and paths.

{
  "format": {
    "quotes": "single"
  }
}
Option Behaviour
single Single quotes: '
double Double quotes: "
any Do not change

Default: "double"

Align

Indents elements to align by type.

{
  "format": {
    "align": {
      "mnemonic": 8,
      "operands": 16,
      "comment": 48,
      "operator": 0,
      "value": 0,
      "indentStyle": "space",
      "tabSize": 8,
      "autoExtend": "line"
    }
  }
}

(defaults)

Property Description
mnemonic Position of instruction/directive mnemonic and size e.g. move.w,include.
operands Position of operands e.g. d0,d1.
comment Position of comment following statement. Comments on their own line are not affected.
operator Position of = character in constant assignment
value Position of value in constant assignment
standaloneComment Position / behaviour of comment with no other elements on the same line.
indentStyle Character to use for indent - tab or space.
tabSize Width of tab character to calculate positions when using tab indent style.
autoExtend Behaviour when a component exceeds the available space between positions. See below.

Options for standaloneComment:

Option Behaviour
"nearest" Align to nearest element position (default). E.g. if current position is closest to mnemonic it snaps to that column
"ignore" Don't align
elementName Align to named element position e.g. "label", "mnemonic", "operands"
number Numeric literal position

Options for autoExtend:

Option Behaviour
line Adjust the position for the affected line only
block Adjust the position, maintaining alignment for all lines within the same block i.e. code separated by two or more line breaks
file Adjust the position, maintaining alignment for all lines in the source file

Trim whitespace

Remove trailing whitespace from lines?

{
  "format": {
    "trimWhitespace": true
  }
}

default: true

Final new line

Require line break on final line?

{
  "format": {
    "finalNewLine": true
  }
}

End-of-line character

New line type: lf, cr, crlf

{
  "format": {
    "endOfLine": "lf"
  }
}

default: lf

TODO

  • Full documentation for 68010+ instructions
  • Diagnostics
    • Instruction signatures
  • Amiga or other platform specific docs?

License

This project is made available under the MIT License.

m68k-lsp's People

Contributors

dependabot[bot] avatar grahambates avatar themkat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

themkat

m68k-lsp's Issues

Node v18 "fetch" causes exception on LSP startup

[ERROR][2022-07-11 17:08:08] .../vim/lsp/rpc.lua:420	"rpc"	"m68k-lsp-server"	"stderr"	"(node:43562) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time\n(Use `node --trace-warnings ...` to show where the warning was created)\n"
[ERROR][2022-07-11 17:08:08] .../vim/lsp/rpc.lua:420	"rpc"	"m68k-lsp-server"	"stderr"	"node:internal/deps/undici/undici:4832\n            throw new TypeError(\"Failed to parse URL from \" + input, { cause: err });\n                  ^\n\nTypeError: Failed to parse URL from /opt/homebrew/lib/node_modules/m68k-lsp-server/node_modules/web-tree-sitter/tree-sitter.wasm\n    at new Request (node:internal/deps/undici/undici:4832:19)\n    at Agent.fetch2 (node:internal/deps/undici/undici:5524:29)\n    ... 4 lines matching cause stack trace ...\n    at new Promise (<anonymous>)\n    at Parser.init (/opt/homebrew/lib/node_modules/m68k-lsp-server/node_modules/web-tree-sitter/tree-sitter.js:1:332)\n    at $l (/opt/homebrew/lib/node_modules/m68k-lsp-server/out/server.js:300:2915)\n    at /opt/homebrew/lib/node_modules/m68k-lsp-server/out/server.js:300:3197 {\n  [cause]: TypeError [ERR_INVALID_URL]: Invalid URL\n      at new NodeError (node:internal/errors:388:5)\n      at URL.onParseError (node:internal/url:564:9)\n      at new URL (node:internal/url:644:5)\n      at new Request (node:internal/deps/undici/undici:4830:25)\n      at Agent.fetch2 (node:internal/deps/undici/undici:5524:29)\n      at Object.fetch (node:internal/deps/undici/undici:6351:20)\n      at fetch (node:internal/bootstrap/pre_execution:197:25)\n      at /opt/homebrew/lib/node_modules/m68k-lsp-server/node_modules/web-tree-sitter/tree-sitter.js:1:15192\n      at /opt/homebrew/lib/node_modules/m68k-lsp-server/node_modules/web-tree-sitter/tree-sitter.js:1:15413\n      at new Promise (<anonymous>) {\n    input: '/opt/homebrew/lib/node_modules/m68k-lsp-server/node_modules/web-tree-sitter/tree-sitter.wasm',\n    code: 'ERR_INVALID_URL'\n  }\n}\n\nNode.js v18.5.0\n"

Seems to be the same issue as referenced here: emscripten-core/emscripten#16913

I was able to workaround by adjusting the first line of m68k-lsp-server (the npm package) to include the --no-experimental-fetch argument:

#!/usr/bin/env node --no-experimental-fetch
require("./out/server");

Maybe building with an updated emscripten would fix this (it seems like the issue was addressed on their side), but otherwise it seems my workaround works perfectly fine as well.

Parser errors though code translates properly

Thanks for this language server, exactly what was looking for right now.

I am trying to use this in nvim to compile some Amiga code targeting 68020 and am getting some Parser errors. Here's a test case, which compiles but produces that error:

    machine  68020
start:
    move.w    .base(pc,d7.l*2),d1
    end

Cannot open included file in VS Code

When using the Visual Studio Code plugin, you get an error when trying to open an included file by clicking on it, because the path the plugin presents to VS Code is escaped.

To reproduce this issue, create a directory c:\tmp and put two files in it, test.asm and test.inc.

; test.inc

somelabel    equ 1
; test.asm

   include "test.inc"

If you open test.asm in Visual Studio Code and set the file type to M68k-Assembly, you'll see that "test.inc" is a hyperlink.

image

If you ctrl+click on this hyperlink it should open the test.inc file, but instead you get this error:

image

As you can see, the problem is that the path is escaped and so VS Code cannot find the file.

Parser and formatter get confused by backslashes in macro code

Perhaps the backspaces are not being treated literally, but are instead considered part of an escape sequence? A couple examples in my code:

; Original line
                CFX_\1FILL
; Gets formatted as this
                CFX_            \1FILL

; Original line
Color\<REPTN>:  dc.l               0
; Gets formatted as this
Color:\                                                          <REPTN>:  dc.l               0

The parser also reports a "Parser error" diagnostic when trying to parse the original line, despite it being valid.

Parser error in some valid label names

In this code snippet:

ATKPARAM      MACRO
atk\<REPTN>_\1: rs.b       \2
              ENDM

The label name is valid, but the parser reports an error. When auto-formatting, the formatter inserts a colon after the underscore, which seems to indicate it thinks the label ends after the underscore but before the \1.

It seems any label name containing some characters, then a macro parameter, then an underscore, then another macro parameter, causes the parser error.

More tests/examples on label names:

something\1_\2 errors.

\1\1_\2 errors.

\1_\2 does not error.

Smarter formatting of comment lines

One thing I've noticed is that auto-formatting the document always results in comments being pushed to the "comments" column as specified, but typically, this is only desirable for an inline comment, i.e. one that follows an operand. Oftentimes, there is a line containing only a comment that is desirable to keep either left-aligned, or aligned with the mnemonic below it.

A few suggestions on possibly good ways of handling this:

  1. Do not format lines that contain only a comment, or only whitespace and a comment
  2. Add a separate align setting for lines that contain only comments
  3. Add a format setting to always align comment-only lines with the mnemonic column, or even the column of choice (mnemonic/operands/operator/value). This would solve the problem of comments losing their alignment if an extra-long symbol name pushes the mnemonic column back a tab.
  4. Have different behaviors or settings for comments prepended with either ; or *, giving the user control over formatting based on comment character used (maybe not ideal if not all assemblers interpret both these characters as comments)

What do you think about these?

Possibility of enhancing LSP features using vasm

2022-05-08_00-54-37

This idea is inspired by prb28/vscode-amiga-assembly, which parses vasm output on build and uses it for linting. I tested something similar by writing a script that parses the vasm output into a standard errorformat, then giving it to a generic lint plugin.

More importantly, vasm seamlessly builds to WebAssembly (using emscripten) and can be run via node without any issues. Aside from linting, maybe there is some other good functionality that could be leveraged, such as using its symbol map/listing file generation to provide information about symbol definitions to the LSP?

This is kind of an open-ended suggestion, because I'm not sure how far you're planning to go with your own parser, or what gaps you think could be filled with vasm. But I thought I would mention my findings in case you think it might be useful to explore.

`onDidChangeConfiguration` overwrites all settings instead of just changed settings, breaking Helix support

async onDidChangeConfiguration() {
const newConfig = await this.ctx.connection.workspace.getConfiguration(
"m68k"
);
this.ctx.config = mergeDefaults(newConfig);
}

According to LSP spec, onDidChangeConfiguration only provides changed settings, but the above function assumes that all settings are being provided.

This discrepancy causes m68k-lsp-server to not work properly in the Helix editor, because Helix sends multiple onDidChangeConfiguration calls which causes the LSP to overwrite the user's initialization options with the defaults.

I confirmed that this workaround successfully fixes Helix support:

  async onDidChangeConfiguration() {
    const oldConfig = this.ctx.config;
    const newConfig = await this.ctx.connection.workspace.getConfiguration(
      "m68k"
    );
    this.ctx.config = {
      ...oldConfig,
      ...newConfig,
      format: {
        ...oldConfig.format,
        ...newConfig?.format,
        align: {
          ...oldConfig.format.align,
          ...newConfig?.format?.align,
        },
      },
      vasm: {
        ...oldConfig.vasm,
        ...newConfig?.vasm,
      },
    };
  }

Parser error when escaping backslash in a macro

Another instance of macros defeating the parser with their strange syntax exceptions.

Basically, vasm lets you access local labels from out of scope using the convention global_label\.local_label. In macros, the backslash is used for special macro parameters, so it needs to be escaped for other uses. That means in macros, local labels are referenced like global_label\\.local_label, which results in the LSP parser error.

SETLOCAL    MACRO
_LOCAL      SET         \1\\.local    ; Parser error
            ENDM

test_function:
            nop
.local:     nop
            rts

test_function2:
            SETLOCAL    test_function
            lea         _LOCAL,a0
            rts

"Go to definition" doesn't work on macros with qualifiers

With vasm (not sure about other assemblers), macros can have size qualifiers, e.g. SOMEMACRO.w, which is argument \0 in the macro.

So, you can do something like this:

SOMEMACRO   MACRO
            IFC         \0,"b"
ATKPRMOFF   SET         ATKPRMOFF+1
            ENDIF
            IFC         \0,"w"
ATKPRMOFF   SET         ATKPRMOFF+2
            ENDIF
            IFC         \0,"l"
ATKPRMOFF   SET         ATKPRMOFF+4
            ENDIF
            ENDM

Then, somewhere else in your program, you can call the macro like:

    SOMEMACRO.w

Problem is, if you use SOMEMACRO.w and use the LSP's "go to definition", it fails to find the definition. "Go to definition" on SOMEMACRO works fine.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.