LSP server for the LOGO programming language, written in Kotlin.
- Syntax highlighting — semantic tokens for keywords, commands, variables, numbers, strings, comments, operators
- Go-to-declaration — Ctrl+Click on
:varnamejumps to MAKE/LOCAL/parameter definition; on procedure name jumps to TO definition - Diagnostics — parse errors shown as red squiggles
- Completion — context-aware:
:triggers variable names (scoped), otherwise suggests builtin commands, user procedures, keywords
Requires JDK 17+.
./gradlew test # run tests (118 cases)
./gradlew shadowJar # build fat JAR
java -jar app/build/libs/logo-lsp.jar # start LSP server (stdio)- Install the LSP4IJ plugin
- Settings → Languages & Frameworks → Language Servers → Add
- Command:
java -jar /path/to/logo-lsp.jar - File association:
*.logo
Source text → Lexer → Tokens → Parser → AST (with spans) → LSP features
- Full document sync — re-lex and re-parse on every edit (LOGO files are small)
- Two-pass parsing — Pass 1 scans procedure arities, Pass 2 parses with known arities (needed for arity-dependent parsing)
- AST with spans — every node tracks source position for cursor-to-node mapping
- It turns out that organizing GoToDeclaration in LOGO is quite challenging, since there is no distinction between declaration and assignment of a variable. I came up with a compromise: I search for all MAKE keywords across procedures and control-flow constructs, then sort and filter them. First come MAKEs local to the procedure, then those that are definitely on the execution path, followed by those hidden behind conditions, and finally non-local MAKEs from all other procedures. Determining exactly which MAKEs are executed at the cursor would require solving the Halting Problem.
- LSP4J supports asynchronous computations and incremental parsing, but I've decided that support such things would be overengineering for this task.
app/src/main/kotlin/logo/
├── Main.kt # LSP stdio launcher
├── lexer/
│ ├── Token.kt # Token types + Token data class
│ └── Lexer.kt # Single-pass scanner
├── parser/
│ ├── Ast.kt # AST nodes + walk() + findNodePath()
│ └── Parser.kt # Two-pass recursive descent
├── analysis/
│ └── BuiltinCommands.kt # ~60 builtin commands with arities
├── lsp/
│ ├── LogoLanguageServer.kt # LSP entry point, capabilities
│ ├── LogoTextDocumentService.kt # Document events + feature dispatch
│ ├── LogoWorkspaceService.kt # No-op
│ └── DocumentManager.kt # Document state + reparse pipeline
└── features/
├── SemanticTokensProvider.kt # Tokens → delta-encoded semantic tokens
├── GoToDeclarationProvider.kt # Cursor position → definition location
├── DiagnosticsProvider.kt # Parse errors → LSP diagnostics
└── CompletionProvider.kt # Scoped variable + command completion