@razdolbai/merls 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.serena/memories/conventions.md +6 -0
- package/.serena/memories/core.md +8 -0
- package/.serena/memories/memory_maintenance.md +33 -0
- package/.serena/memories/suggested_commands.md +4 -0
- package/.serena/memories/task_completion.md +7 -0
- package/.serena/memories/tech_stack.md +6 -0
- package/.serena/project.yml +132 -0
- package/AGENTS.md +63 -0
- package/README.md +137 -0
- package/dist/src/asm/diagnostics.js +202 -0
- package/dist/src/asm/document.js +26 -0
- package/dist/src/asm/expression.js +163 -0
- package/dist/src/asm/lexer.js +122 -0
- package/dist/src/asm/local-labels.js +140 -0
- package/dist/src/asm/metadata.js +101 -0
- package/dist/src/asm/parser.js +118 -0
- package/dist/src/asm/symbols.js +40 -0
- package/dist/src/asm/syntax.js +44 -0
- package/dist/src/asm/workspace.js +73 -0
- package/dist/src/cli.js +21 -0
- package/dist/src/index.js +4 -0
- package/dist/src/lsp/completion.js +32 -0
- package/dist/src/lsp/diagnostics.js +63 -0
- package/dist/src/lsp/document-symbols.js +80 -0
- package/dist/src/lsp/hover.js +75 -0
- package/dist/src/lsp/symbol-navigation.js +181 -0
- package/dist/src/lsp/workspace-symbols.js +17 -0
- package/dist/src/server.js +77 -0
- package/dist/test/bootstrap.test.js +11 -0
- package/dist/test/cli-contract.test.js +74 -0
- package/dist/test/coc-config.test.js +21 -0
- package/dist/test/completion.test.js +126 -0
- package/dist/test/definition-references.test.js +126 -0
- package/dist/test/diagnostics.test.js +66 -0
- package/dist/test/document-model.test.js +30 -0
- package/dist/test/document-symbol.test.js +107 -0
- package/dist/test/expression.test.js +100 -0
- package/dist/test/fixture-corpus.test.js +33 -0
- package/dist/test/hover.test.js +142 -0
- package/dist/test/lexer.test.js +53 -0
- package/dist/test/line-parser.test.js +67 -0
- package/dist/test/local-labels.test.js +43 -0
- package/dist/test/metadata.test.js +27 -0
- package/dist/test/publish-diagnostics.test.js +137 -0
- package/dist/test/run-tests.js +132 -0
- package/dist/test/server-entrypoint.test.js +14 -0
- package/dist/test/server-initialize.test.js +77 -0
- package/dist/test/symbols.test.js +37 -0
- package/dist/test/syntax-shape.test.js +18 -0
- package/dist/test/workspace-symbol.test.js +113 -0
- package/dist/test/workspace.test.js +24 -0
- package/examples/coc-settings.json +18 -0
- package/package.json +26 -0
- package/publish.ps1 +9 -0
- package/src/asm/diagnostics.ts +294 -0
- package/src/asm/document.ts +43 -0
- package/src/asm/expression.ts +242 -0
- package/src/asm/lexer.ts +197 -0
- package/src/asm/local-labels.ts +204 -0
- package/src/asm/metadata.ts +150 -0
- package/src/asm/parser.ts +197 -0
- package/src/asm/symbols.ts +55 -0
- package/src/asm/syntax.ts +76 -0
- package/src/asm/workspace.ts +105 -0
- package/src/cli.ts +24 -0
- package/src/index.ts +1 -0
- package/src/lsp/completion.ts +42 -0
- package/src/lsp/diagnostics.ts +82 -0
- package/src/lsp/document-symbols.ts +111 -0
- package/src/lsp/hover.ts +90 -0
- package/src/lsp/symbol-navigation.ts +244 -0
- package/src/lsp/workspace-symbols.ts +24 -0
- package/src/server.ts +121 -0
- package/test/bootstrap.test.ts +7 -0
- package/test/cli-contract.test.ts +94 -0
- package/test/coc-config.test.ts +28 -0
- package/test/completion.test.ts +151 -0
- package/test/definition-references.test.ts +152 -0
- package/test/diagnostics.test.ts +129 -0
- package/test/document-model.test.ts +29 -0
- package/test/document-symbol.test.ts +131 -0
- package/test/expression.test.ts +111 -0
- package/test/fixture-corpus.test.ts +33 -0
- package/test/fixtures/invalid/65816-bank-ops.asm +17 -0
- package/test/fixtures/invalid/65816-long-addressing.asm +26 -0
- package/test/fixtures/valid/merlin32-linkscript.asm +16 -0
- package/test/fixtures/valid/merlin32-main-6502.asm +103 -0
- package/test/fixtures/valid/smoke-test.asm +7 -0
- package/test/hover.test.ts +175 -0
- package/test/lexer.test.ts +87 -0
- package/test/line-parser.test.ts +69 -0
- package/test/local-labels.test.ts +47 -0
- package/test/metadata.test.ts +27 -0
- package/test/publish-diagnostics.test.ts +206 -0
- package/test/run-tests.ts +139 -0
- package/test/server-entrypoint.test.ts +11 -0
- package/test/server-initialize.test.ts +101 -0
- package/test/smoke/run-smoke.ps1 +177 -0
- package/test/smoke/vimrc +17 -0
- package/test/symbols.test.ts +41 -0
- package/test/syntax-shape.test.ts +18 -0
- package/test/workspace-symbol.test.ts +139 -0
- package/test/workspace.test.ts +29 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
- TDD is mandatory from the start; implementation tasks begin with a failing test or fixture.
|
|
2
|
+
- Keep parser-based MVP; do not introduce external `merlin32` execution in the first implementation pass.
|
|
3
|
+
- Separate parser, symbol-resolution, and LSP layers.
|
|
4
|
+
- TypeScript naming: `camelCase` for values/functions, `PascalCase` for types/classes, kebab-case for config/example filenames.
|
|
5
|
+
- Fixture convention: positive fixtures cover supported Merlin-style 6502 syntax; negative fixtures cover unsupported 65816 syntax and expected diagnostics.
|
|
6
|
+
- Task completion rule: if behavior, structure, workflow, or contributor expectations change, update both `README.md` and `AGENTS.md`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
- Completed MVP repo; current durable docs are \README.md\, \PLAN.md\, \AGENTS.md\.
|
|
2
|
+
- Purpose: standalone LSP server for Merlin-style 6502 assembly, primary editor target coc.nvim, stdio transport.
|
|
3
|
+
- Scope invariant: 6502-only. 65816 syntax is out of scope and should be diagnosed as unsupported.
|
|
4
|
+
- Implementation plan and phase breakdown live in \PLAN.md\. All planned tasks are completed.
|
|
5
|
+
- Contributor rules and doc-maintenance requirement live in \AGENTS.md\.
|
|
6
|
+
- For stack and workspace layout, read \mem:tech_stack\.
|
|
7
|
+
- For code/test/doc conventions, read \mem:conventions\.
|
|
8
|
+
- For done criteria and required verification/docs updates, read \mem:task_completion\.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Memory Maintenance
|
|
2
|
+
|
|
3
|
+
## Discovery Model
|
|
4
|
+
|
|
5
|
+
- Core principle: progressive discovery through references, building a graph of memories.
|
|
6
|
+
- Initially, agents are provided with the list of all memories (names only).
|
|
7
|
+
- Agents should read `mem:core` as the top-level entry point (graph root).
|
|
8
|
+
This memory should contain references to other memories covering major project domains.
|
|
9
|
+
The referenced memories shall, in turn, shall contain references to even more specific memories, and so on.
|
|
10
|
+
The depth of the graph shall depend on the project complexity.
|
|
11
|
+
- Use topics/folders to group related memories in order to make the content structure explicit.
|
|
12
|
+
Folders can mirror project structure (e.g. modules like frontend/backend) or topics like debugging, architecture, etc.
|
|
13
|
+
- Memory references must use a mem: prefix inside backticks, e.g. `mem:frontend/core`.
|
|
14
|
+
The surrounding text should clearly indicate when to read the memory/which content to expect.
|
|
15
|
+
The text should provide more precise guidance than the memory name alone,
|
|
16
|
+
i.e. avoid a reference like "frontend debugging: `mem:frontend/debugging` and instead make clear which aspects of frontend debugging are covered.
|
|
17
|
+
- Memories themselves should not contain information about when to read them; this is the responsibility of the referring memory.
|
|
18
|
+
|
|
19
|
+
## Style
|
|
20
|
+
|
|
21
|
+
Dense agent notes, not prose docs. Prefer invariants, terse bullets.
|
|
22
|
+
Avoid obvious context, rationale, and examples unless they prevent likely mistakes.
|
|
23
|
+
Keep guidance durable and generalizable, not task-local.
|
|
24
|
+
|
|
25
|
+
## Add/update threshold
|
|
26
|
+
|
|
27
|
+
Add or update memories only with stable, non-obvious project conventions that avoid complex rediscovery in the future.
|
|
28
|
+
Do not add: quick-read facts; generic language/framework knowledge; one-off task notes; volatile line-level details; behavior likely to change soon.
|
|
29
|
+
|
|
30
|
+
## Maintenance Actions
|
|
31
|
+
|
|
32
|
+
- Renaming memories: References are updated automatically if handled via Serena's memory rename tool.
|
|
33
|
+
- Checking for stale memories (e.g. after deletion): Call `serena memories check` for a report.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
- Windows shell is PowerShell; use PowerShell forms when documenting local commands.
|
|
2
|
+
- Current useful repo commands are read-only/doc-oriented: `Get-ChildItem`, `Get-Content README.md`, `Get-Content PLAN.md`, `Get-Content AGENTS.md`.
|
|
3
|
+
- Planned commands after Phase 1 scaffolding: `npm install`, `npm run build`, `npm test`, `npm run dev` (or equivalent stdio server runner).
|
|
4
|
+
- Do not assume npm scripts exist until `package.json` is added.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
- A coding task is not done until relevant tests/fixtures added first by TDD are passing under the repo's actual toolchain.
|
|
2
|
+
- The project has a runnable build/test command (\
|
|
3
|
+
pm run build\, \
|
|
4
|
+
pm test\); use them.
|
|
5
|
+
- Always review whether the task changed project behavior, structure, workflow, or contributor expectations.
|
|
6
|
+
- If yes, update both \README.md\ and \AGENTS.md\ in the same task.
|
|
7
|
+
- Preserve scope invariants: 6502 support only, 65816 rejection explicit, coc.nvim/stdio integration target unchanged unless the docs and plan are updated together.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
- Node.js runtime, TypeScript implementation, \scode-languageserver\ for LSP.
|
|
2
|
+
- Server model: standalone process over stdio; coc.nvim launches it via standard \languageserver\ config.
|
|
3
|
+
- Workspace shape: \src/\ for implementation, \ est/\ for automated tests, \xamples/\ for coc.nvim config/examples, dedicated fixture directories for assembly corpora.
|
|
4
|
+
- Built via \
|
|
5
|
+
pm run build\ and tested via \
|
|
6
|
+
pm test\.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# the name by which the project can be referenced within Serena
|
|
2
|
+
project_name: "merls"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# list of languages for which language servers are started; choose from:
|
|
6
|
+
# al angular ansible bash clojure
|
|
7
|
+
# cpp cpp_ccls crystal csharp csharp_omnisharp
|
|
8
|
+
# dart elixir elm erlang fortran
|
|
9
|
+
# fsharp go groovy haskell haxe
|
|
10
|
+
# hlsl html java json julia
|
|
11
|
+
# kotlin lean4 lua luau markdown
|
|
12
|
+
# matlab msl nix ocaml pascal
|
|
13
|
+
# perl php php_phpactor powershell python
|
|
14
|
+
# python_jedi python_ty r rego ruby
|
|
15
|
+
# ruby_solargraph rust scala scss solidity
|
|
16
|
+
# svelte swift systemverilog terraform toml
|
|
17
|
+
# typescript typescript_vts vue yaml zig
|
|
18
|
+
# (This list may be outdated. For the current list, see values of Language enum here:
|
|
19
|
+
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
|
20
|
+
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
|
21
|
+
# Note:
|
|
22
|
+
# - For C, use cpp
|
|
23
|
+
# - For JavaScript, use typescript
|
|
24
|
+
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
|
|
25
|
+
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
|
|
26
|
+
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
|
|
27
|
+
# - For Free Pascal/Lazarus, use pascal
|
|
28
|
+
# Special requirements:
|
|
29
|
+
# Some languages require additional setup/installations.
|
|
30
|
+
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
|
|
31
|
+
# When using multiple languages, the first language server that supports a given file will be used for that file.
|
|
32
|
+
# The first language is the default language and the respective language server will be used as a fallback.
|
|
33
|
+
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
|
34
|
+
languages: []
|
|
35
|
+
|
|
36
|
+
# the encoding used by text files in the project
|
|
37
|
+
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
|
38
|
+
encoding: "utf-8"
|
|
39
|
+
|
|
40
|
+
# line ending convention to use when writing source files.
|
|
41
|
+
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
|
|
42
|
+
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
|
|
43
|
+
line_ending:
|
|
44
|
+
|
|
45
|
+
# The language backend to use for this project.
|
|
46
|
+
# If not set, the global setting from serena_config.yml is used.
|
|
47
|
+
# Valid values: LSP, JetBrains
|
|
48
|
+
# Note: the backend is fixed at startup. If a project with a different backend
|
|
49
|
+
# is activated post-init, an error will be returned.
|
|
50
|
+
language_backend:
|
|
51
|
+
|
|
52
|
+
# whether to use project's .gitignore files to ignore files
|
|
53
|
+
ignore_all_files_in_gitignore: true
|
|
54
|
+
|
|
55
|
+
# advanced configuration option allowing to configure language server-specific options.
|
|
56
|
+
# Maps the language key to the options.
|
|
57
|
+
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
|
|
58
|
+
# No documentation on options means no options are available.
|
|
59
|
+
ls_specific_settings: {}
|
|
60
|
+
|
|
61
|
+
# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos).
|
|
62
|
+
# Paths can be absolute or relative to the project root.
|
|
63
|
+
# Each folder is registered as an LSP workspace folder, enabling language servers to discover
|
|
64
|
+
# symbols and references across package boundaries.
|
|
65
|
+
# Currently supported for: TypeScript.
|
|
66
|
+
# Example:
|
|
67
|
+
# additional_workspace_folders:
|
|
68
|
+
# - ../sibling-package
|
|
69
|
+
# - ../shared-lib
|
|
70
|
+
additional_workspace_folders: []
|
|
71
|
+
|
|
72
|
+
# list of additional paths to ignore in this project.
|
|
73
|
+
# Same syntax as gitignore, so you can use * and **.
|
|
74
|
+
# Note: global ignored_paths from serena_config.yml are also applied additively.
|
|
75
|
+
ignored_paths: []
|
|
76
|
+
|
|
77
|
+
# whether the project is in read-only mode
|
|
78
|
+
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
|
79
|
+
# Added on 2025-04-18
|
|
80
|
+
read_only: false
|
|
81
|
+
|
|
82
|
+
# list of tool names to exclude.
|
|
83
|
+
# This extends the existing exclusions (e.g. from the global configuration)
|
|
84
|
+
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
|
85
|
+
excluded_tools: []
|
|
86
|
+
|
|
87
|
+
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
|
88
|
+
# This extends the existing inclusions (e.g. from the global configuration).
|
|
89
|
+
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
|
90
|
+
included_optional_tools: []
|
|
91
|
+
|
|
92
|
+
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
|
93
|
+
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
|
94
|
+
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
|
95
|
+
fixed_tools: []
|
|
96
|
+
|
|
97
|
+
# list of mode names that are to be activated by default, overriding the setting in the global configuration.
|
|
98
|
+
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
|
99
|
+
# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
|
|
100
|
+
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
|
101
|
+
# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
|
|
102
|
+
# for this project.
|
|
103
|
+
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
|
104
|
+
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
|
105
|
+
default_modes:
|
|
106
|
+
|
|
107
|
+
# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
|
|
108
|
+
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
|
109
|
+
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
|
110
|
+
added_modes:
|
|
111
|
+
|
|
112
|
+
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
|
113
|
+
# (contrary to the memories, which are loaded on demand).
|
|
114
|
+
initial_prompt: ""
|
|
115
|
+
|
|
116
|
+
# time budget (seconds) per tool call for the retrieval of additional symbol information
|
|
117
|
+
# such as docstrings or parameter information.
|
|
118
|
+
# This overrides the corresponding setting in the global configuration; see the documentation there.
|
|
119
|
+
# If null or missing, use the setting from the global configuration.
|
|
120
|
+
symbol_info_budget:
|
|
121
|
+
|
|
122
|
+
# list of regex patterns which, when matched, mark a memory entry as read‑only.
|
|
123
|
+
# Extends the list from the global configuration, merging the two lists.
|
|
124
|
+
read_only_memory_patterns: []
|
|
125
|
+
|
|
126
|
+
# list of regex patterns for memories to completely ignore.
|
|
127
|
+
# Matching memories will not appear in list_memories or activate_project output
|
|
128
|
+
# and cannot be accessed via read_memory or write_memory.
|
|
129
|
+
# To access ignored memory files, use the read_file tool on the raw file path.
|
|
130
|
+
# Extends the list from the global configuration, merging the two lists.
|
|
131
|
+
# Example: ["_archive/.*", "_episodes/.*"]
|
|
132
|
+
ignored_memory_patterns: []
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Repository Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Structure & Module Organization
|
|
4
|
+
|
|
5
|
+
This repository is in the bootstrap stage. Today, the main documents are `README.md` for scope and `PLAN.md` for the implementation checklist. The planned runtime is Node.js with TypeScript and `vscode-languageserver`.
|
|
6
|
+
|
|
7
|
+
When the workspace is scaffolded, keep code under `src/`, tests under `test/`, and editor integration examples under `examples/` such as `examples/coc-settings.json`. Store parser fixtures in a dedicated test-fixture area and separate valid 6502 cases from invalid 65816 cases.
|
|
8
|
+
|
|
9
|
+
The current bootstrap server entrypoint is `src/server.ts`, and the packaged CLI entrypoint is `src/cli.ts`, which compiles to `dist/src/cli.js`.
|
|
10
|
+
The repository now includes `examples/coc-settings.json` as the baseline coc.nvim launch example, targeting `dist/src/cli.js --stdio` with `.git` and `package.json` root markers.
|
|
11
|
+
The initial positive fixture corpus now lives under `test/fixtures/valid/` and is transcribed from upstream Merlin32 sources.
|
|
12
|
+
The initial negative fixture corpus now lives under `test/fixtures/invalid/` and captures unsupported 65816-only syntax.
|
|
13
|
+
Shared 6502 opcode and Merlin directive metadata now lives under `src/asm/metadata.ts`.
|
|
14
|
+
Shared token-kind and line-shape metadata now lives under `src/asm/syntax.ts`.
|
|
15
|
+
The current lexer implementation now lives under `src/asm/lexer.ts` and is covered by fixture-driven unit tests.
|
|
16
|
+
The current expression parser now lives under `src/asm/expression.ts` and is covered by unit tests for numeric forms, modifiers, arithmetic, and indexed operands.
|
|
17
|
+
The current line parser now lives under `src/asm/parser.ts` and classifies equates, instructions, directives, data lines, and malformed input.
|
|
18
|
+
The current document model now lives under `src/asm/document.ts` and preserves line-by-line structure while collecting malformed-line errors.
|
|
19
|
+
The current symbol collector now lives under `src/asm/symbols.ts` and indexes labels, equates, and named storage/data definitions.
|
|
20
|
+
The current local-label resolver now lives under `src/asm/local-labels.ts` and resolves Merlin `]local` and `:local` labels within the nearest global-label scope.
|
|
21
|
+
The current workspace indexer now lives under `src/asm/workspace.ts` and follows `asm`/`put`/`use` directives across the local fixture corpus.
|
|
22
|
+
The current diagnostics pass now lives under `src/asm/diagnostics.ts` and reports duplicate symbols, unresolved references, malformed lines, and unsupported 65816-only syntax.
|
|
23
|
+
The current `textDocument/documentSymbol` provider is wired through `src/server.ts` and `src/lsp/document-symbols.ts`.
|
|
24
|
+
The current `workspace/symbol` provider is wired through `src/server.ts` and `src/lsp/workspace-symbols.ts` over the open-document symbol set.
|
|
25
|
+
The current `textDocument/definition` and `textDocument/references` handlers are wired through `src/server.ts` and `src/lsp/symbol-navigation.ts`.
|
|
26
|
+
The current `textDocument/hover` handler is wired through `src/server.ts` and `src/lsp/hover.ts`.
|
|
27
|
+
The current `textDocument/completion` handler is wired through `src/server.ts` and `src/lsp/completion.ts`.
|
|
28
|
+
The current `textDocument/publishDiagnostics` path is wired through `src/server.ts` and `src/lsp/diagnostics.ts`, with full-document sync on open/change so editor clients receive live parser and resolver diagnostics.
|
|
29
|
+
The current packaged CLI entrypoint lives under `src/cli.ts` and is covered by a compiled stdio launch-contract integration test.
|
|
30
|
+
The coc.nvim smoke test now lives under `test/smoke/` with a headless Vim runner (`run-smoke.ps1`) and a minimal vimrc that isolates the test from the user's real Vim configuration.
|
|
31
|
+
|
|
32
|
+
## Build, Test, and Development Commands
|
|
33
|
+
|
|
34
|
+
The repository now includes the initial Node/TypeScript workspace scaffold:
|
|
35
|
+
|
|
36
|
+
- `npm install`: install project dependencies.
|
|
37
|
+
- `npm run build`: compile TypeScript sources to `dist/`.
|
|
38
|
+
- `npm test`: build the project and run the current test suite.
|
|
39
|
+
- `npm run dev`: run the TypeScript compiler in watch mode during bootstrap work.
|
|
40
|
+
|
|
41
|
+
The supported stdio launch contract is now `merls --stdio`, with `node dist/src/cli.js --stdio` as the equivalent local-development invocation.
|
|
42
|
+
|
|
43
|
+
## Coding Style & Naming Conventions
|
|
44
|
+
|
|
45
|
+
Use TypeScript throughout the implementation. Prefer small modules with explicit types and single-purpose exports. Use `camelCase` for variables and functions, `PascalCase` for types and classes, and kebab-case for example/config file names.
|
|
46
|
+
|
|
47
|
+
Keep parser, symbol, and LSP layers separate. Name tests and fixtures after the behavior they cover, for example `parser.labels.test.ts` or `fixtures/invalid/65816-long-a.asm`.
|
|
48
|
+
|
|
49
|
+
## Testing Guidelines
|
|
50
|
+
|
|
51
|
+
TDD is mandatory in this repository: add or extend a failing test or fixture before implementation. Positive fixtures must cover supported Merlin-style 6502 syntax. Negative fixtures must explicitly cover unsupported 65816 syntax and expected diagnostics.
|
|
52
|
+
|
|
53
|
+
Prefer focused unit tests for lexer/parser behavior and integration tests for LSP requests such as `initialize`, hover, and definition.
|
|
54
|
+
|
|
55
|
+
The current bootstrap test suite already includes compiled-stdio integration checks for the packaged CLI contract, `initialize`, and diagnostics publication.
|
|
56
|
+
|
|
57
|
+
## Commit & Pull Request Guidelines
|
|
58
|
+
|
|
59
|
+
This workspace does not currently include accessible git history, so no local commit convention can be inferred from prior commits. Use short, imperative commit subjects such as `Add lexer token fixtures`.
|
|
60
|
+
|
|
61
|
+
Task completion includes documentation maintenance. When a change affects repository behavior, structure, workflow, or contributor expectations, update both `README.md` and `AGENTS.md` in the same task.
|
|
62
|
+
|
|
63
|
+
PRs should describe the user-visible behavior change, list the tests added or updated, and include sample diagnostics or editor screenshots when LSP behavior changes. Keep PRs scoped to one phase task or one coherent feature.
|
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# merls
|
|
2
|
+
|
|
3
|
+
`merls` is a planned language server for **Merlin-style 6502 assembly** with **coc.nvim** as the primary editor target.
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
This repository now has an initial Node.js and TypeScript workspace scaffold, a packaged stdio CLI contract, and the MVP LSP feature set. All planned tasks in `PLAN.md` are complete, including the coc.nvim smoke test.
|
|
8
|
+
|
|
9
|
+
## Goals
|
|
10
|
+
|
|
11
|
+
- provide a standalone LSP server over stdio
|
|
12
|
+
- work cleanly with coc.nvim through standard language-server configuration
|
|
13
|
+
- support Merlin-style 6502 source structure, symbols, directives, and expressions
|
|
14
|
+
- deliver useful editing features before deeper assembler integration
|
|
15
|
+
|
|
16
|
+
## Scope
|
|
17
|
+
|
|
18
|
+
### In scope
|
|
19
|
+
|
|
20
|
+
- 6502-only Merlin-style assembly
|
|
21
|
+
- parser-based diagnostics for the MVP
|
|
22
|
+
- core LSP features such as:
|
|
23
|
+
- diagnostics
|
|
24
|
+
- hover
|
|
25
|
+
- completion
|
|
26
|
+
- go to definition
|
|
27
|
+
- find references
|
|
28
|
+
- document symbols
|
|
29
|
+
- workspace symbols
|
|
30
|
+
|
|
31
|
+
### Out of scope
|
|
32
|
+
|
|
33
|
+
- 65816 support
|
|
34
|
+
- assembler-backed diagnostics in the first implementation pass
|
|
35
|
+
- coc.nvim-specific plugin code for the MVP
|
|
36
|
+
|
|
37
|
+
## Planned implementation
|
|
38
|
+
|
|
39
|
+
- **runtime:** Node.js
|
|
40
|
+
- **language:** TypeScript
|
|
41
|
+
- **LSP library:** `vscode-languageserver`
|
|
42
|
+
- **process model:** standalone stdio server
|
|
43
|
+
- **development style:** TDD from the start
|
|
44
|
+
|
|
45
|
+
## Development workflow
|
|
46
|
+
|
|
47
|
+
- `npm install`: install project dependencies
|
|
48
|
+
- `npm run build`: compile the TypeScript sources into `dist/`
|
|
49
|
+
- `npm test`: build the project and run the current test suite
|
|
50
|
+
- `npm run dev`: run the TypeScript compiler in watch mode during bootstrap work
|
|
51
|
+
- `pwsh test/smoke/run-smoke.ps1`: run the headless Vim + coc.nvim smoke test (requires Vim with coc.nvim installed via vim-plug)
|
|
52
|
+
|
|
53
|
+
The packaged CLI entrypoint now lives at `dist/src/cli.js`.
|
|
54
|
+
|
|
55
|
+
## CLI contract
|
|
56
|
+
|
|
57
|
+
The supported stdio launch contract is `merls --stdio`.
|
|
58
|
+
|
|
59
|
+
For local development from this checkout, the equivalent compiled command is `node dist/src/cli.js --stdio`.
|
|
60
|
+
|
|
61
|
+
## Roadmap
|
|
62
|
+
|
|
63
|
+
The current plan is organized into these phases:
|
|
64
|
+
|
|
65
|
+
1. bootstrap the Node/TypeScript workspace
|
|
66
|
+
2. define the 6502 Merlin syntax corpus and shared metadata
|
|
67
|
+
3. implement the lexer, parser, and document model
|
|
68
|
+
4. implement symbols, include handling, and diagnostics
|
|
69
|
+
5. add the MVP LSP features
|
|
70
|
+
6. package and verify coc.nvim integration
|
|
71
|
+
|
|
72
|
+
See `PLAN.md` for the full checklist.
|
|
73
|
+
|
|
74
|
+
## coc.nvim target
|
|
75
|
+
|
|
76
|
+
The intended integration model is a standard `languageserver` entry in `coc-settings.json` that launches `merls` over stdio.
|
|
77
|
+
|
|
78
|
+
An initial example lives in `examples/coc-settings.json`. The current bootstrap flow is:
|
|
79
|
+
|
|
80
|
+
1. run `npm install`
|
|
81
|
+
2. run `npm run build`
|
|
82
|
+
3. point coc.nvim at `dist/src/cli.js --stdio`
|
|
83
|
+
|
|
84
|
+
The bundled example uses `node` plus an absolute path to the packaged CLI, passes `--stdio`, uses `package.json` and `.git` as root markers, and currently targets the `asm` filetype.
|
|
85
|
+
|
|
86
|
+
The intended `languageserver` shape is:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"languageserver": {
|
|
91
|
+
"merls": {
|
|
92
|
+
"command": "node",
|
|
93
|
+
"args": [
|
|
94
|
+
"C:/path/to/merls/dist/src/cli.js",
|
|
95
|
+
"--stdio"
|
|
96
|
+
],
|
|
97
|
+
"rootPatterns": [
|
|
98
|
+
".git",
|
|
99
|
+
"package.json"
|
|
100
|
+
],
|
|
101
|
+
"filetypes": [
|
|
102
|
+
"asm"
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Replace the example path with the local checkout or installed package location on your machine.
|
|
110
|
+
|
|
111
|
+
## Development notes
|
|
112
|
+
|
|
113
|
+
- TDD is mandatory for implementation work in this repository.
|
|
114
|
+
- Shared 6502 opcode and Merlin directive metadata now lives in `src/asm/metadata.ts`.
|
|
115
|
+
- Shared token-kind and line-shape metadata now lives in `src/asm/syntax.ts`.
|
|
116
|
+
- The current lexer implementation now lives in `src/asm/lexer.ts` and is exercised against the shared fixture corpus.
|
|
117
|
+
- The current expression parser now lives in `src/asm/expression.ts` and covers numeric forms, unary modifiers, arithmetic, and indexed operand fragments.
|
|
118
|
+
- The current line parser now lives in `src/asm/parser.ts` and classifies equates, instructions, directives, data lines, and malformed input.
|
|
119
|
+
- The current document model now lives in `src/asm/document.ts` and preserves line-by-line structure while collecting malformed-line errors.
|
|
120
|
+
- The current symbol collector now lives in `src/asm/symbols.ts` and indexes labels, equates, and named storage/data definitions.
|
|
121
|
+
- The current local-label resolver now lives in `src/asm/local-labels.ts` and resolves Merlin `]local` and `:local` labels within the nearest global-label scope.
|
|
122
|
+
- The current workspace indexer now lives in `src/asm/workspace.ts` and follows `asm`/`put`/`use` directives across the local fixture corpus.
|
|
123
|
+
- The current diagnostics pass now lives in `src/asm/diagnostics.ts` and reports duplicate symbols, unresolved references, malformed lines, and unsupported 65816-only syntax.
|
|
124
|
+
- The current `textDocument/documentSymbol` provider is wired through `src/server.ts` and `src/lsp/document-symbols.ts`.
|
|
125
|
+
- The current `workspace/symbol` provider is wired through `src/server.ts` and `src/lsp/workspace-symbols.ts` over the open-document symbol set.
|
|
126
|
+
- The current `textDocument/definition` and `textDocument/references` handlers are wired through `src/server.ts` and `src/lsp/symbol-navigation.ts`.
|
|
127
|
+
- The current `textDocument/hover` handler is wired through `src/server.ts` and `src/lsp/hover.ts`.
|
|
128
|
+
- The current `textDocument/completion` handler is wired through `src/server.ts` and `src/lsp/completion.ts`.
|
|
129
|
+
- The current `textDocument/publishDiagnostics` path is wired through `src/server.ts` and `src/lsp/diagnostics.ts`, with full-document sync on open and change so coc.nvim receives live parser and resolver errors.
|
|
130
|
+
- The packaged CLI entrypoint now lives in `src/cli.ts`, compiles to `dist/src/cli.js`, and supports the explicit stdio contract `merls --stdio`.
|
|
131
|
+
- The checked-in coc.nvim example now targets `dist/src/cli.js --stdio` and includes root detection for `.git` and `package.json`.
|
|
132
|
+
- The positive fixture corpus now starts with transcribed Merlin32 material under `test/fixtures/valid/`.
|
|
133
|
+
- Positive fixtures should cover supported 6502 Merlin-style syntax.
|
|
134
|
+
- The negative fixture corpus now starts with explicit 65816-only samples under `test/fixtures/invalid/`.
|
|
135
|
+
- Negative fixtures should explicitly cover unsupported 65816 syntax.
|
|
136
|
+
- The current bootstrap test harness is intentionally minimal and runs against compiled output to avoid depending on editor or browser tooling.
|
|
137
|
+
- The current integration coverage reaches through stdio `initialize`, the packaged CLI contract, completion, hover, symbol navigation, and diagnostics publication.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectWorkspaceDiagnostics = collectWorkspaceDiagnostics;
|
|
4
|
+
const local_labels_1 = require("./local-labels");
|
|
5
|
+
const metadata_1 = require("./metadata");
|
|
6
|
+
function collectWorkspaceDiagnostics(documents) {
|
|
7
|
+
const diagnostics = [];
|
|
8
|
+
const globalSymbols = new Set();
|
|
9
|
+
const symbolRecords = [];
|
|
10
|
+
for (const entry of documents) {
|
|
11
|
+
for (const symbol of collectGlobalDefinitions(entry.document, entry.filePath)) {
|
|
12
|
+
symbolRecords.push(symbol);
|
|
13
|
+
globalSymbols.add(symbol.name);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
diagnostics.push(...collectDuplicateSymbolDiagnostics(symbolRecords));
|
|
17
|
+
for (const entry of documents) {
|
|
18
|
+
diagnostics.push(...collectMalformedDiagnostics(entry.filePath, entry.document), ...collectUnsupportedDiagnostics(entry.filePath, entry.document), ...collectUnresolvedDiagnostics(entry.filePath, entry.document, globalSymbols));
|
|
19
|
+
}
|
|
20
|
+
return diagnostics;
|
|
21
|
+
}
|
|
22
|
+
function collectDuplicateSymbolDiagnostics(symbolRecords) {
|
|
23
|
+
const firstDefinitionByName = new Map();
|
|
24
|
+
const diagnostics = [];
|
|
25
|
+
for (const symbol of symbolRecords) {
|
|
26
|
+
const firstDefinition = firstDefinitionByName.get(symbol.name);
|
|
27
|
+
if (firstDefinition === undefined) {
|
|
28
|
+
firstDefinitionByName.set(symbol.name, symbol);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
diagnostics.push({
|
|
32
|
+
filePath: symbol.filePath,
|
|
33
|
+
line: symbol.line,
|
|
34
|
+
code: "duplicate-symbol",
|
|
35
|
+
message: `Duplicate symbol ${symbol.name}; first defined at line ${firstDefinition.line}`
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return diagnostics;
|
|
39
|
+
}
|
|
40
|
+
function collectMalformedDiagnostics(filePath, document) {
|
|
41
|
+
return document.errors.map((error) => ({
|
|
42
|
+
filePath,
|
|
43
|
+
line: error.line,
|
|
44
|
+
code: "malformed-line",
|
|
45
|
+
message: error.message
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
function collectUnsupportedDiagnostics(filePath, document) {
|
|
49
|
+
const diagnostics = [];
|
|
50
|
+
for (const line of document.lines) {
|
|
51
|
+
const unsupportedDirective = getUnsupportedDirective(line.node);
|
|
52
|
+
if (unsupportedDirective !== null) {
|
|
53
|
+
diagnostics.push({
|
|
54
|
+
filePath,
|
|
55
|
+
line: line.line,
|
|
56
|
+
code: "unsupported-65816",
|
|
57
|
+
message: `Unsupported 65816 directive: ${unsupportedDirective}`
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const unsupportedText = getUnsupportedTextPattern(line.node.text);
|
|
61
|
+
if (unsupportedText !== null) {
|
|
62
|
+
diagnostics.push({
|
|
63
|
+
filePath,
|
|
64
|
+
line: line.line,
|
|
65
|
+
code: "unsupported-65816",
|
|
66
|
+
message: `Unsupported 65816 syntax: ${unsupportedText}`
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return diagnostics;
|
|
71
|
+
}
|
|
72
|
+
function collectUnresolvedDiagnostics(filePath, document, globalSymbols) {
|
|
73
|
+
const diagnostics = [];
|
|
74
|
+
const localScope = (0, local_labels_1.resolveLocalLabels)(document);
|
|
75
|
+
for (const line of document.lines) {
|
|
76
|
+
for (const reference of findExpressionReferences(line.node)) {
|
|
77
|
+
if (reference.startsWith("]") || reference.startsWith(":")) {
|
|
78
|
+
const localKey = `${reference}@${line.line}`;
|
|
79
|
+
const localDefinitionKey = [...localScope.definitions.keys()].find((key) => key.startsWith(`${reference}@`));
|
|
80
|
+
if (!localScope.references.has(localKey) && localDefinitionKey === undefined) {
|
|
81
|
+
diagnostics.push({
|
|
82
|
+
filePath,
|
|
83
|
+
line: line.line,
|
|
84
|
+
code: "unresolved-reference",
|
|
85
|
+
message: `Unresolved local reference ${reference}`
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!globalSymbols.has(reference)) {
|
|
91
|
+
diagnostics.push({
|
|
92
|
+
filePath,
|
|
93
|
+
line: line.line,
|
|
94
|
+
code: "unresolved-reference",
|
|
95
|
+
message: `Unresolved reference ${reference}`
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return diagnostics;
|
|
101
|
+
}
|
|
102
|
+
function collectGlobalDefinitions(document, filePath) {
|
|
103
|
+
const symbols = [];
|
|
104
|
+
for (const line of document.lines) {
|
|
105
|
+
const name = getGlobalDefinitionName(line.node);
|
|
106
|
+
if (name === null) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
symbols.push({
|
|
110
|
+
name,
|
|
111
|
+
line: line.line,
|
|
112
|
+
filePath
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return symbols;
|
|
116
|
+
}
|
|
117
|
+
function getGlobalDefinitionName(node) {
|
|
118
|
+
if (node.shape === "equate" && !isLocalLabel(node.label)) {
|
|
119
|
+
return node.label;
|
|
120
|
+
}
|
|
121
|
+
if (node.shape === "labelOnly" && !isLocalLabel(node.label)) {
|
|
122
|
+
return node.label;
|
|
123
|
+
}
|
|
124
|
+
if (node.shape === "instruction" && node.label !== null && !isLocalLabel(node.label)) {
|
|
125
|
+
return node.label;
|
|
126
|
+
}
|
|
127
|
+
if (node.shape === "directive" && node.label !== null && !isLocalLabel(node.label)) {
|
|
128
|
+
return node.label;
|
|
129
|
+
}
|
|
130
|
+
if (node.shape === "data" && node.label !== null && !isLocalLabel(node.label)) {
|
|
131
|
+
return node.label;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
function findExpressionReferences(node) {
|
|
136
|
+
if (node.shape === "instruction" && node.operand !== null) {
|
|
137
|
+
return findReferencesInOperand(node.operand);
|
|
138
|
+
}
|
|
139
|
+
if (node.shape === "directive" && node.operand !== null) {
|
|
140
|
+
const directive = metadata_1.directiveTable.get(node.directive);
|
|
141
|
+
if (directive?.kind === "include" || directive?.kind === "build") {
|
|
142
|
+
if (node.directive === "typ") {
|
|
143
|
+
const knownAliases = new Set([
|
|
144
|
+
"txt", "bin", "sys", "bas", "var", "rel",
|
|
145
|
+
"lib", "s16", "rtl", "exe", "pif", "tif",
|
|
146
|
+
"nda", "cda", "tol", "dvr", "ldf", "fst"
|
|
147
|
+
]);
|
|
148
|
+
return findReferencesInExpression(node.operand).filter((ref) => !knownAliases.has(ref.toLowerCase()));
|
|
149
|
+
}
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
return findReferencesInExpression(node.operand);
|
|
153
|
+
}
|
|
154
|
+
if (node.shape === "equate") {
|
|
155
|
+
return findReferencesInExpression(node.expression);
|
|
156
|
+
}
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
function findReferencesInOperand(operand) {
|
|
160
|
+
return findReferencesInExpression(operand.expression);
|
|
161
|
+
}
|
|
162
|
+
function findReferencesInExpression(expression) {
|
|
163
|
+
switch (expression.kind) {
|
|
164
|
+
case "identifier":
|
|
165
|
+
return [expression.value];
|
|
166
|
+
case "modifier":
|
|
167
|
+
return findReferencesInExpression(expression.expression);
|
|
168
|
+
case "binary":
|
|
169
|
+
return [
|
|
170
|
+
...findReferencesInExpression(expression.left),
|
|
171
|
+
...findReferencesInExpression(expression.right)
|
|
172
|
+
];
|
|
173
|
+
default:
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function getUnsupportedDirective(node) {
|
|
178
|
+
if (node.shape !== "directive") {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
const directive = metadata_1.directiveTable.get(node.directive);
|
|
182
|
+
if (directive?.supported === false) {
|
|
183
|
+
return node.directive;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
function getUnsupportedTextPattern(text) {
|
|
188
|
+
const trimmed = text.trim().toLowerCase();
|
|
189
|
+
if (/^(pea|mvn|mvp)\b/.test(trimmed)) {
|
|
190
|
+
return trimmed.split(/\s+/, 1)[0] ?? trimmed;
|
|
191
|
+
}
|
|
192
|
+
if (trimmed.includes("^")) {
|
|
193
|
+
return "^";
|
|
194
|
+
}
|
|
195
|
+
if (trimmed.includes("|")) {
|
|
196
|
+
return "|";
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
function isLocalLabel(name) {
|
|
201
|
+
return name.startsWith("]") || name.startsWith(":");
|
|
202
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseDocument = parseDocument;
|
|
4
|
+
const parser_1 = require("./parser");
|
|
5
|
+
function parseDocument(source) {
|
|
6
|
+
const parsedLines = (0, parser_1.parseSourceLines)(source);
|
|
7
|
+
const lines = [];
|
|
8
|
+
const errors = [];
|
|
9
|
+
parsedLines.forEach((node, line) => {
|
|
10
|
+
lines.push({
|
|
11
|
+
line,
|
|
12
|
+
node
|
|
13
|
+
});
|
|
14
|
+
if (node.shape === "malformed") {
|
|
15
|
+
errors.push({
|
|
16
|
+
line,
|
|
17
|
+
text: node.text,
|
|
18
|
+
message: node.message
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
lines,
|
|
24
|
+
errors
|
|
25
|
+
};
|
|
26
|
+
}
|