151 lines
4.1 KiB
TypeScript
151 lines
4.1 KiB
TypeScript
import { TextDocument } from 'vscode-languageserver-textdocument'
|
|
import { buildDiagnostics } from './diagnostics'
|
|
import { buildSemanticTokens, TOKEN_MODIFIERS, TOKEN_TYPES } from './semanticTokens'
|
|
import { parser } from '../../../src/parser/shrimp'
|
|
import { Compiler } from '../../../src/compiler/compiler'
|
|
import {
|
|
InitializeResult,
|
|
TextDocuments,
|
|
TextDocumentSyncKind,
|
|
createConnection,
|
|
ProposedFeatures,
|
|
CompletionItemKind,
|
|
} from 'vscode-languageserver/node'
|
|
|
|
const connection = createConnection(ProposedFeatures.all)
|
|
const documents = new TextDocuments(TextDocument)
|
|
documents.listen(connection)
|
|
|
|
// Server capabilities
|
|
connection.onInitialize(handleInitialize)
|
|
|
|
// Language features
|
|
connection.languages.semanticTokens.on(handleSemanticTokens)
|
|
documents.onDidChangeContent(handleDocumentChange)
|
|
connection.onCompletion(handleCompletion)
|
|
|
|
// Debug commands
|
|
connection.onRequest('shrimp/parseTree', handleParseTree)
|
|
connection.onRequest('shrimp/bytecode', handleBytecode)
|
|
|
|
// Start listening
|
|
connection.listen()
|
|
|
|
// ============================================================================
|
|
// Handler implementations
|
|
// ============================================================================
|
|
|
|
function handleInitialize(): InitializeResult {
|
|
connection.console.log('🦐 Server initialized with capabilities')
|
|
const result: InitializeResult = {
|
|
capabilities: {
|
|
textDocumentSync: TextDocumentSyncKind.Full,
|
|
completionProvider: {
|
|
triggerCharacters: ['.'],
|
|
},
|
|
semanticTokensProvider: {
|
|
legend: {
|
|
tokenTypes: TOKEN_TYPES,
|
|
tokenModifiers: TOKEN_MODIFIERS,
|
|
},
|
|
full: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
function handleSemanticTokens(params: any) {
|
|
const document = documents.get(params.textDocument.uri)
|
|
if (!document) return { data: [] }
|
|
|
|
const data = buildSemanticTokens(document)
|
|
return { data }
|
|
}
|
|
|
|
function handleDocumentChange(change: any) {
|
|
const textDocument = change.document
|
|
const diagnostics = buildDiagnostics(textDocument)
|
|
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics })
|
|
}
|
|
|
|
function handleCompletion(params: any) {
|
|
const keywords = ['if', 'else', 'do', 'end', 'and', 'or', 'true', 'false', 'null']
|
|
|
|
return keywords.map((keyword) => ({
|
|
label: keyword,
|
|
kind: CompletionItemKind.Keyword,
|
|
}))
|
|
}
|
|
|
|
function handleParseTree(params: { uri: string }) {
|
|
connection.console.log(`🦐 Parse tree requested for: ${params.uri}`)
|
|
const document = documents.get(params.uri)
|
|
if (!document) return 'Document not found'
|
|
|
|
const text = document.getText()
|
|
const tree = parser.parse(text)
|
|
const cursor = tree.cursor()
|
|
|
|
let formatted = ''
|
|
let depth = 0
|
|
|
|
const printNode = () => {
|
|
const nodeName = cursor.name
|
|
const nodeText = text.slice(cursor.from, cursor.to)
|
|
const indent = ' '.repeat(depth)
|
|
|
|
formatted += `${indent}${nodeName}`
|
|
if (nodeText) {
|
|
const escapedText = nodeText.replace(/\n/g, '\\n').replace(/\r/g, '\\r')
|
|
formatted += ` "${escapedText}"`
|
|
}
|
|
formatted += '\n'
|
|
}
|
|
|
|
const traverse = (): void => {
|
|
printNode()
|
|
|
|
if (cursor.firstChild()) {
|
|
depth++
|
|
do {
|
|
traverse()
|
|
} while (cursor.nextSibling())
|
|
cursor.parent()
|
|
depth--
|
|
}
|
|
}
|
|
|
|
traverse()
|
|
return formatted
|
|
}
|
|
|
|
function handleBytecode(params: { uri: string }) {
|
|
connection.console.log(`🦐 Bytecode requested for: ${params.uri}`)
|
|
const document = documents.get(params.uri)
|
|
if (!document) return 'Document not found'
|
|
|
|
try {
|
|
const text = document.getText()
|
|
const compiler = new Compiler(text)
|
|
|
|
// Format bytecode as readable string
|
|
let output = 'Bytecode:\n\n'
|
|
const bytecode = compiler.bytecode
|
|
|
|
output += bytecode.instructions
|
|
.map((op, i) => `${i.toString().padStart(4)}: ${JSON.stringify(op)}`)
|
|
.join('\n')
|
|
|
|
// Strip ANSI color codes
|
|
output = output.replace(/\x1b\[[0-9;]*m/g, '')
|
|
|
|
return output
|
|
} catch (error) {
|
|
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
// Strip ANSI color codes from error message too
|
|
return `Compilation failed: ${errorMsg.replace(/\x1b\[[0-9;]*m/g, '')}`
|
|
}
|
|
}
|