extension is working!
This commit is contained in:
parent
f4cbe54a88
commit
2d7f0dbe25
|
|
@ -280,13 +280,27 @@ export class Compiler {
|
|||
|
||||
const opValue = input.slice(operator.from, operator.to)
|
||||
switch (opValue) {
|
||||
case '+=': instructions.push(['ADD']); break
|
||||
case '-=': instructions.push(['SUB']); break
|
||||
case '*=': instructions.push(['MUL']); break
|
||||
case '/=': instructions.push(['DIV']); break
|
||||
case '%=': instructions.push(['MOD']); break
|
||||
case '+=':
|
||||
instructions.push(['ADD'])
|
||||
break
|
||||
case '-=':
|
||||
instructions.push(['SUB'])
|
||||
break
|
||||
case '*=':
|
||||
instructions.push(['MUL'])
|
||||
break
|
||||
case '/=':
|
||||
instructions.push(['DIV'])
|
||||
break
|
||||
case '%=':
|
||||
instructions.push(['MOD'])
|
||||
break
|
||||
default:
|
||||
throw new CompilerError(`Unknown compound operator: ${opValue}`, operator.from, operator.to)
|
||||
throw new CompilerError(
|
||||
`Unknown compound operator: ${opValue}`,
|
||||
operator.from,
|
||||
operator.to
|
||||
)
|
||||
}
|
||||
|
||||
// DUP and store (same as regular assignment)
|
||||
|
|
@ -304,10 +318,8 @@ export class Compiler {
|
|||
}
|
||||
|
||||
case terms.FunctionDef: {
|
||||
const { paramNames, bodyNodes, catchVariable, catchBody, finallyBody } = getFunctionDefParts(
|
||||
node,
|
||||
input
|
||||
)
|
||||
const { paramNames, bodyNodes, catchVariable, catchBody, finallyBody } =
|
||||
getFunctionDefParts(node, input)
|
||||
const instructions: ProgramItem[] = []
|
||||
const functionLabel: Label = `.func_${this.fnLabelCount++}`
|
||||
const afterLabel: Label = `.after_${functionLabel}`
|
||||
|
|
@ -330,7 +342,13 @@ export class Compiler {
|
|||
if (catchVariable || finallyBody) {
|
||||
// If function has catch or finally, wrap body in try/catch/finally
|
||||
instructions.push(
|
||||
...this.#compileTryCatchFinally(compileFunctionBody, catchVariable, catchBody, finallyBody, input)
|
||||
...this.#compileTryCatchFinally(
|
||||
compileFunctionBody,
|
||||
catchVariable,
|
||||
catchBody,
|
||||
finallyBody,
|
||||
input
|
||||
)
|
||||
)
|
||||
} else {
|
||||
instructions.push(...compileFunctionBody())
|
||||
|
|
|
|||
|
|
@ -304,9 +304,12 @@ describe('default params', () => {
|
|||
})
|
||||
|
||||
test.skip('dict default', () => {
|
||||
expect('make-person = do person=[name=Bob age=60]: person end; make-person')
|
||||
.toEvaluateTo({ name: 'Bob', age: 60 })
|
||||
expect('make-person = do person=[name=Bob age=60]: person end; make-person [name=Jon age=21]')
|
||||
.toEvaluateTo({ name: 'Jon', age: 21 })
|
||||
expect('make-person = do person=[name=Bob age=60]: person end; make-person').toEvaluateTo({
|
||||
name: 'Bob',
|
||||
age: 60,
|
||||
})
|
||||
expect(
|
||||
'make-person = do person=[name=Bob age=60]: person end; make-person [name=Jon age=21]'
|
||||
).toEvaluateTo({ name: 'Jon', age: 21 })
|
||||
})
|
||||
})
|
||||
3
vscode-extension/.gitignore
vendored
3
vscode-extension/.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
node_modules
|
||||
dist
|
||||
client/dist
|
||||
server/dist
|
||||
*.vsix
|
||||
|
|
|
|||
3
vscode-extension/.vscode/launch.json
vendored
3
vscode-extension/.vscode/launch.json
vendored
|
|
@ -9,7 +9,8 @@
|
|||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
"${workspaceFolder}/client/dist/**/*.js",
|
||||
"${workspaceFolder}/server/dist/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "bun: compile"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@
|
|||
"workspaces": {
|
||||
"": {
|
||||
"name": "shrimp",
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "^9.0.1",
|
||||
"vscode-languageserver": "^9.0.1",
|
||||
"vscode-languageserver-textdocument": "^1.0.12",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "22.x",
|
||||
"@types/vscode": "^1.105.0",
|
||||
|
|
@ -15,8 +20,28 @@
|
|||
|
||||
"@types/vscode": ["@types/vscode@1.105.0", "", {}, "sha512-Lotk3CTFlGZN8ray4VxJE7axIyLZZETQJVWi/lYoUVQuqfRxlQhVOfoejsD2V3dVXPSbS15ov5ZyowMAzgUqcw=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
|
||||
|
||||
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="],
|
||||
|
||||
"vscode-languageclient": ["vscode-languageclient@9.0.1", "", { "dependencies": { "minimatch": "^5.1.0", "semver": "^7.3.7", "vscode-languageserver-protocol": "3.17.5" } }, "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA=="],
|
||||
|
||||
"vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="],
|
||||
|
||||
"vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="],
|
||||
|
||||
"vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="],
|
||||
|
||||
"vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
74
vscode-extension/client/src/extension.ts
Normal file
74
vscode-extension/client/src/extension.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import {
|
||||
LanguageClient,
|
||||
LanguageClientOptions,
|
||||
ServerOptions,
|
||||
TransportKind,
|
||||
} from 'vscode-languageclient/node'
|
||||
import * as vscode from 'vscode'
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const serverModule = context.asAbsolutePath('server/dist/server.js')
|
||||
|
||||
const serverOptions: ServerOptions = {
|
||||
run: { module: serverModule, transport: TransportKind.ipc },
|
||||
debug: { module: serverModule, transport: TransportKind.ipc },
|
||||
}
|
||||
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
documentSelector: [{ scheme: 'file', language: 'shrimp' }],
|
||||
}
|
||||
|
||||
const client = new LanguageClient(
|
||||
'shrimpLanguageServer',
|
||||
'Shrimp Language Server',
|
||||
serverOptions,
|
||||
clientOptions
|
||||
)
|
||||
|
||||
client.start()
|
||||
context.subscriptions.push(client)
|
||||
|
||||
// Command: Show Parse Tree
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('shrimp.showParseTree', async () => {
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor || editor.document.languageId !== 'shrimp') {
|
||||
vscode.window.showErrorMessage('No active Shrimp file')
|
||||
return
|
||||
}
|
||||
|
||||
const result = await client.sendRequest<string>('shrimp/parseTree', {
|
||||
uri: editor.document.uri.toString(),
|
||||
})
|
||||
|
||||
const doc = await vscode.workspace.openTextDocument({
|
||||
content: result,
|
||||
language: 'text',
|
||||
})
|
||||
await vscode.window.showTextDocument(doc, { preview: false })
|
||||
})
|
||||
)
|
||||
|
||||
// Command: Show Bytecode
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('shrimp.showBytecode', async () => {
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor || editor.document.languageId !== 'shrimp') {
|
||||
vscode.window.showErrorMessage('No active Shrimp file')
|
||||
return
|
||||
}
|
||||
|
||||
const result = await client.sendRequest<string>('shrimp/bytecode', {
|
||||
uri: editor.document.uri.toString(),
|
||||
})
|
||||
|
||||
const doc = await vscode.workspace.openTextDocument({
|
||||
content: result,
|
||||
language: 'text',
|
||||
})
|
||||
await vscode.window.showTextDocument(doc, { preview: false })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "shrimp",
|
||||
"version": "0.0.1",
|
||||
"main": "./dist/extension.js",
|
||||
"main": "./client/dist/extension.js",
|
||||
"devDependencies": {
|
||||
"@types/vscode": "^1.105.0",
|
||||
"@types/node": "22.x",
|
||||
|
|
@ -28,7 +28,29 @@
|
|||
"[shrimp]": {
|
||||
"editor.semanticHighlighting.enabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "shrimp.showParseTree",
|
||||
"title": "Shrimp: Show Parse Tree"
|
||||
},
|
||||
{
|
||||
"command": "shrimp.showBytecode",
|
||||
"title": "Shrimp: Show Bytecode"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "shrimp.showParseTree",
|
||||
"key": "alt+k alt+i",
|
||||
"when": "editorLangId == shrimp"
|
||||
},
|
||||
{
|
||||
"command": "shrimp.showBytecode",
|
||||
"key": "alt+k alt+,",
|
||||
"when": "editorLangId == shrimp"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Language support for Shrimp shell scripting language",
|
||||
"displayName": "Shrimp",
|
||||
|
|
@ -39,9 +61,16 @@
|
|||
"publisher": "shrimp-lang",
|
||||
"scripts": {
|
||||
"vscode:prepublish": "bun run package",
|
||||
"compile": "bun build src/extension.ts --outdir dist --target node --format cjs --external vscode",
|
||||
"watch": "bun build src/extension.ts --outdir dist --target node --format cjs --external vscode --watch",
|
||||
"package": "bun build src/extension.ts --outdir dist --target node --format cjs --external vscode --minify",
|
||||
"compile": "bun run compile:client && bun run compile:server",
|
||||
"compile:client": "bun build client/src/extension.ts --outdir client/dist --target node --format cjs --external vscode",
|
||||
"compile:server": "bun build server/src/server.ts --outdir server/dist --target node --format cjs --external vscode-languageserver --external vscode-languageserver-textdocument",
|
||||
"watch": "bun run compile:client --watch",
|
||||
"package": "bun run compile:client --minify && bun run compile:server --minify",
|
||||
"check-types": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "^9.0.1",
|
||||
"vscode-languageserver": "^9.0.1",
|
||||
"vscode-languageserver-textdocument": "^1.0.12"
|
||||
}
|
||||
}
|
||||
93
vscode-extension/server/src/diagnostics.ts
Normal file
93
vscode-extension/server/src/diagnostics.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { TextDocument, Position } from 'vscode-languageserver-textdocument'
|
||||
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver/node'
|
||||
import { parser } from '../../../src/parser/shrimp'
|
||||
import { Compiler } from '../../../src/compiler/compiler'
|
||||
import { CompilerError } from '../../../src/compiler/compilerError'
|
||||
|
||||
export const buildDiagnostics = (textDocument: TextDocument): Diagnostic[] => {
|
||||
const text = textDocument.getText()
|
||||
const diagnostics = getParseErrors(textDocument)
|
||||
|
||||
if (diagnostics.length > 0) {
|
||||
return diagnostics
|
||||
}
|
||||
const diagnostic = getCompilerError(text)
|
||||
if (diagnostic) return [diagnostic]
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
const getCompilerError = (text: string): Diagnostic | undefined => {
|
||||
try {
|
||||
new Compiler(text)
|
||||
} catch (e) {
|
||||
if (!(e instanceof CompilerError)) {
|
||||
return unknownDiagnostic(getErrorMessage(e))
|
||||
}
|
||||
|
||||
const lineInfo = e.lineAtPosition(text)!
|
||||
const cause = e.cause ? ` Cause: ${e.cause}` : ''
|
||||
const message = e.message
|
||||
|
||||
if (!lineInfo) {
|
||||
return unknownDiagnostic(message + cause)
|
||||
}
|
||||
|
||||
const diagnostic: Diagnostic = {
|
||||
severity: DiagnosticSeverity.Error,
|
||||
range: {
|
||||
start: { line: lineInfo.lineNumber, character: lineInfo.columnStart },
|
||||
end: { line: lineInfo.lineNumber, character: lineInfo.columnEnd },
|
||||
},
|
||||
message: `Compiler error: ${message}${cause}`,
|
||||
source: 'shrimp',
|
||||
}
|
||||
return diagnostic
|
||||
}
|
||||
}
|
||||
|
||||
const unknownDiagnostic = (message: string): Diagnostic => {
|
||||
const diagnostic: Diagnostic = {
|
||||
severity: DiagnosticSeverity.Error,
|
||||
range: {
|
||||
start: { line: 0, character: 0 },
|
||||
end: { line: -1, character: -1 },
|
||||
},
|
||||
message,
|
||||
source: 'shrimp',
|
||||
}
|
||||
return diagnostic
|
||||
}
|
||||
|
||||
const getParseErrors = (textDocument: TextDocument): Diagnostic[] => {
|
||||
const tree = parser.parse(textDocument.getText())
|
||||
|
||||
const ranges: { start: Position; end: Position }[] = []
|
||||
tree.iterate({
|
||||
enter(n) {
|
||||
if (n.type.isError) {
|
||||
ranges.push({
|
||||
start: textDocument.positionAt(n.from),
|
||||
end: textDocument.positionAt(n.to),
|
||||
})
|
||||
return false
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return ranges.map((range) => {
|
||||
return {
|
||||
range,
|
||||
severity: DiagnosticSeverity.Error,
|
||||
message: 'Parse error: Invalid syntax',
|
||||
source: 'shrimp',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getErrorMessage = (error: unknown): string => {
|
||||
if (error instanceof Error) {
|
||||
return error.message
|
||||
}
|
||||
return String(error)
|
||||
}
|
||||
101
vscode-extension/server/src/semanticTokens.ts
Normal file
101
vscode-extension/server/src/semanticTokens.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { parser } from '../../../src/parser/shrimp'
|
||||
import * as Terms from '../../../src/parser/shrimp.terms'
|
||||
import { SyntaxNode } from '@lezer/common'
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||
import { SemanticTokensBuilder, SemanticTokenTypes } from 'vscode-languageserver/node'
|
||||
|
||||
export const TOKEN_TYPES = [
|
||||
SemanticTokenTypes.function,
|
||||
SemanticTokenTypes.variable,
|
||||
SemanticTokenTypes.string,
|
||||
SemanticTokenTypes.number,
|
||||
SemanticTokenTypes.operator,
|
||||
SemanticTokenTypes.keyword,
|
||||
SemanticTokenTypes.parameter,
|
||||
SemanticTokenTypes.property,
|
||||
SemanticTokenTypes.regexp,
|
||||
]
|
||||
|
||||
export const TOKEN_MODIFIERS: string[] = []
|
||||
|
||||
export function buildSemanticTokens(document: TextDocument): number[] {
|
||||
const text = document.getText()
|
||||
const tree = parser.parse(text)
|
||||
const builder = new SemanticTokensBuilder()
|
||||
|
||||
walkTree(tree.topNode, document, builder)
|
||||
|
||||
return builder.build().data
|
||||
}
|
||||
|
||||
// Walk the tree and collect tokens
|
||||
function walkTree(node: SyntaxNode, document: TextDocument, builder: SemanticTokensBuilder) {
|
||||
const tokenType = getTokenType(node.type.id)
|
||||
|
||||
if (tokenType !== undefined) {
|
||||
const start = document.positionAt(node.from)
|
||||
const length = node.to - node.from
|
||||
builder.push(start.line, start.character, length, tokenType, 0)
|
||||
}
|
||||
|
||||
let child = node.firstChild
|
||||
while (child) {
|
||||
walkTree(child, document, builder)
|
||||
child = child.nextSibling
|
||||
}
|
||||
}
|
||||
|
||||
// Map Lezer node IDs to semantic token type indices
|
||||
function getTokenType(nodeTypeId: number): number | undefined {
|
||||
switch (nodeTypeId) {
|
||||
case Terms.FunctionCall:
|
||||
case Terms.FunctionDef:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.function)
|
||||
|
||||
case Terms.Identifier:
|
||||
case Terms.AssignableIdentifier:
|
||||
case Terms.FunctionCallOrIdentifier:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.variable)
|
||||
|
||||
case Terms.String:
|
||||
case Terms.StringFragment:
|
||||
case Terms.Word:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.string)
|
||||
|
||||
case Terms.Number:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.number)
|
||||
|
||||
case Terms.Plus:
|
||||
case Terms.Minus:
|
||||
case Terms.Star:
|
||||
case Terms.Slash:
|
||||
case Terms.Eq:
|
||||
case Terms.EqEq:
|
||||
case Terms.Neq:
|
||||
case Terms.Lt:
|
||||
case Terms.Lte:
|
||||
case Terms.Gt:
|
||||
case Terms.Gte:
|
||||
case Terms.Modulo:
|
||||
case Terms.And:
|
||||
case Terms.Or:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.operator)
|
||||
|
||||
case Terms.keyword:
|
||||
case Terms.Do:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.keyword)
|
||||
|
||||
case Terms.Params:
|
||||
case Terms.NamedParam:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.parameter)
|
||||
|
||||
case Terms.DotGet:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.property)
|
||||
|
||||
case Terms.Regex:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.regexp)
|
||||
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
105
vscode-extension/server/src/server.ts
Normal file
105
vscode-extension/server/src/server.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import { TextDocuments } from 'vscode-languageserver/node'
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||
import { createConnection, ProposedFeatures } from 'vscode-languageserver/node'
|
||||
import { buildDiagnostics } from './diagnostics'
|
||||
import { buildSemanticTokens, TOKEN_MODIFIERS, TOKEN_TYPES } from './semanticTokens'
|
||||
import { parser } from '../../../src/parser/shrimp'
|
||||
import { Compiler } from '../../../src/compiler/compiler'
|
||||
|
||||
const connection = createConnection(ProposedFeatures.all)
|
||||
const documents = new TextDocuments(TextDocument)
|
||||
documents.listen(connection)
|
||||
|
||||
connection.onInitialize(() => {
|
||||
connection.console.log('🦐 Server initialized with capabilities')
|
||||
return {
|
||||
capabilities: {
|
||||
textDocumentSync: 1,
|
||||
semanticTokensProvider: {
|
||||
legend: {
|
||||
tokenTypes: TOKEN_TYPES,
|
||||
tokenModifiers: TOKEN_MODIFIERS,
|
||||
},
|
||||
full: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
connection.languages.semanticTokens.on((params) => {
|
||||
const document = documents.get(params.textDocument.uri)
|
||||
if (!document) {
|
||||
return { data: [] }
|
||||
}
|
||||
|
||||
const data = buildSemanticTokens(document)
|
||||
return { data }
|
||||
})
|
||||
|
||||
documents.onDidChangeContent((change) => {
|
||||
const textDocument = change.document
|
||||
|
||||
const diagnostics = buildDiagnostics(textDocument)
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics })
|
||||
})
|
||||
|
||||
connection.onRequest('shrimp/parseTree', (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 treeString = tree.toString()
|
||||
|
||||
// Format with indentation, without parentheses
|
||||
let formatted = ''
|
||||
let indent = 0
|
||||
for (let i = 0; i < treeString.length; i++) {
|
||||
const char = treeString[i]
|
||||
if (char === '(') {
|
||||
formatted += '\n'
|
||||
indent++
|
||||
formatted += ' '.repeat(indent)
|
||||
} else if (char === ')') {
|
||||
indent--
|
||||
} else if (char === ',') {
|
||||
formatted += '\n'
|
||||
formatted += ' '.repeat(indent)
|
||||
} else {
|
||||
formatted += char
|
||||
}
|
||||
}
|
||||
|
||||
return formatted
|
||||
})
|
||||
|
||||
connection.onRequest('shrimp/bytecode', (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, '')}`
|
||||
}
|
||||
})
|
||||
|
||||
connection.listen()
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import * as vscode from 'vscode'
|
||||
import { ShrimpSemanticTokensProvider, legend } from './semanticTokens'
|
||||
import { parser } from '../../src/parser/shrimp'
|
||||
|
||||
// This method is called when your extension is activated
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
console.log('Shrimp extension is now active!')
|
||||
console.log('Parser loaded:', typeof parser, parser)
|
||||
|
||||
// Test the parser
|
||||
try {
|
||||
const testTree = parser.parse('x = 42')
|
||||
console.log('Parser test successful:', testTree.topNode.toString())
|
||||
} catch (error) {
|
||||
console.error('Parser test failed:', error)
|
||||
}
|
||||
|
||||
// Register semantic tokens provider for Shrimp language
|
||||
const provider = new ShrimpSemanticTokensProvider()
|
||||
const selector: vscode.DocumentSelector = { language: 'shrimp', scheme: 'file' }
|
||||
|
||||
const disposable = vscode.languages.registerDocumentSemanticTokensProvider(selector, provider, legend)
|
||||
console.log('Registered semantic tokens provider:', disposable)
|
||||
context.subscriptions.push(disposable)
|
||||
|
||||
console.log('Legend token types:', legend.tokenTypes)
|
||||
}
|
||||
|
||||
// This method is called when your extension is deactivated
|
||||
export function deactivate() {}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
import * as vscode from 'vscode'
|
||||
import { parser } from '../../src/parser/shrimp'
|
||||
import { Tree, SyntaxNode } from '@lezer/common'
|
||||
|
||||
// Define the token types we'll use
|
||||
const tokenTypes = [
|
||||
'function',
|
||||
'variable',
|
||||
'string',
|
||||
'number',
|
||||
'operator',
|
||||
'keyword',
|
||||
'parameter',
|
||||
'property',
|
||||
'regexp',
|
||||
]
|
||||
|
||||
const tokenModifiers: string[] = []
|
||||
|
||||
export const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers)
|
||||
|
||||
export class ShrimpSemanticTokensProvider implements vscode.DocumentSemanticTokensProvider {
|
||||
async provideDocumentSemanticTokens(
|
||||
document: vscode.TextDocument,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<vscode.SemanticTokens> {
|
||||
try {
|
||||
console.log('provideDocumentSemanticTokens called for:', document.fileName)
|
||||
const tokensBuilder = new vscode.SemanticTokensBuilder(legend)
|
||||
const text = document.getText()
|
||||
console.log('Document text:', text)
|
||||
console.log('About to parse with parser:', typeof parser)
|
||||
const tree: Tree = parser.parse(text)
|
||||
console.log('Parsed tree:', tree.topNode.toString())
|
||||
this.walkTree(tree.topNode, document, tokensBuilder)
|
||||
|
||||
const result = tokensBuilder.build()
|
||||
console.log('Built tokens, data length:', result.data.length)
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('Error in provideDocumentSemanticTokens:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Map Lezer node types to semantic token types
|
||||
walkTree(node: SyntaxNode, document: vscode.TextDocument, builder: vscode.SemanticTokensBuilder) {
|
||||
const tokenType = this.getTokenType(node.type.name)
|
||||
|
||||
if (tokenType !== undefined) {
|
||||
const start = document.positionAt(node.from)
|
||||
const length = node.to - node.from
|
||||
builder.push(start.line, start.character, length, tokenType, 0)
|
||||
}
|
||||
|
||||
// Recursively walk children
|
||||
let child = node.firstChild
|
||||
while (child) {
|
||||
this.walkTree(child, document, builder)
|
||||
child = child.nextSibling
|
||||
}
|
||||
}
|
||||
|
||||
getTokenType(nodeTypeName: string): number | undefined {
|
||||
// Map Lezer node names to VSCode semantic token types
|
||||
switch (nodeTypeName) {
|
||||
case 'FunctionCall':
|
||||
case 'FunctionDef':
|
||||
return tokenTypes.indexOf('function')
|
||||
|
||||
case 'Identifier':
|
||||
case 'AssignableIdentifier':
|
||||
case 'FunctionCallOrIdentifier':
|
||||
return tokenTypes.indexOf('variable')
|
||||
|
||||
case 'String':
|
||||
case 'StringFragment':
|
||||
case 'Word':
|
||||
return tokenTypes.indexOf('string')
|
||||
|
||||
case 'Number':
|
||||
return tokenTypes.indexOf('number')
|
||||
|
||||
case 'Plus':
|
||||
case 'Minus':
|
||||
case 'Star':
|
||||
case 'Slash':
|
||||
case 'Eq':
|
||||
case 'EqEq':
|
||||
case 'Neq':
|
||||
case 'Lt':
|
||||
case 'Lte':
|
||||
case 'Gt':
|
||||
case 'Gte':
|
||||
case 'Modulo':
|
||||
case 'And':
|
||||
case 'Or':
|
||||
return tokenTypes.indexOf('operator')
|
||||
|
||||
case 'keyword':
|
||||
case 'Do':
|
||||
return tokenTypes.indexOf('keyword')
|
||||
|
||||
case 'Params':
|
||||
case 'NamedParam':
|
||||
return tokenTypes.indexOf('parameter')
|
||||
|
||||
case 'DotGet':
|
||||
return tokenTypes.indexOf('property')
|
||||
|
||||
case 'Regex':
|
||||
return tokenTypes.indexOf('regexp')
|
||||
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"outDir": "./dist",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
|
|
@ -11,6 +11,6 @@
|
|||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"include": ["client/src/**/*", "server/src/**/*", "../src/**/*"],
|
||||
"exclude": ["node_modules", "client/dist", "server/dist"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user