step 1 to 5

This commit is contained in:
TM-Squared
2025-08-15 17:26:41 +02:00
parent e1eadcbea8
commit 13e5cb16bb
24 changed files with 5334 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
out
dist
node_modules
.vscode-test/
*.vsix
*.s

5
.vscode-test.mjs Normal file
View File

@@ -0,0 +1,5 @@
import { defineConfig } from '@vscode/test-cli';
export default defineConfig({
files: 'out/test/**/*.test.js',
});

8
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"dbaeumer.vscode-eslint",
"ms-vscode.extension-test-runner"
]
}

21
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,21 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
}
]
}

11
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,11 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
}

20
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

11
.vscodeignore Normal file
View File

@@ -0,0 +1,11 @@
.vscode/**
.vscode-test/**
src/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/eslint.config.mjs
**/*.map
**/*.ts
**/.vscode-test.*

9
CHANGELOG.md Normal file
View File

@@ -0,0 +1,9 @@
# Change Log
All notable changes to the "riscv-asm" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [Unreleased]
- Initial release

71
README.md Normal file
View File

@@ -0,0 +1,71 @@
# riscv-asm README
This is the README for your extension "riscv-asm". After writing up a brief description, we recommend including the following sections.
## Features
Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
For example if there is an image subfolder under your extension project workspace:
\!\[feature X\]\(images/feature-x.png\)
> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
## Requirements
If you have any requirements or dependencies, add a section describing those and how to install and configure them.
## Extension Settings
Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
For example:
This extension contributes the following settings:
* `myExtension.enable`: Enable/disable this extension.
* `myExtension.thing`: Set to `blah` to do something.
## Known Issues
Calling out known issues can help limit users opening duplicate issues against your extension.
## Release Notes
Users appreciate release notes as you update your extension.
### 1.0.0
Initial release of ...
### 1.0.1
Fixed issue #.
### 1.1.0
Added features X, Y, and Z.
---
## Following extension guidelines
Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension.
* [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)
## Working with Markdown
You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux).
* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux).
* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets.
## For more information
* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
**Enjoy!**

98
TODO Normal file
View File

@@ -0,0 +1,98 @@
# TODO - Extension VSCode RISC-V
## Tâches terminées
### Étape 1: Coloration Syntaxique
- [x] Configuration du langage RISC-V (.s, .asm, .rv)
- [x] Grammaire TextMate pour coloration syntaxique
- [x] Support des instructions, registres, directives, commentaires
- [x] Support des nombres (décimal, hexadécimal, binaire)
- [x] Configuration des brackets et auto-fermeture
### Étape 2: Snippets de Base
- [x] Snippets pour instructions arithmétiques (add, addi, sub, etc.)
- [x] Snippets pour instructions mémoire (lw, sw, lb, sb, etc.)
- [x] Snippets pour instructions de branchement (beq, bne, blt, etc.)
- [x] Snippets pour pseudo-instructions (li, la, mv, j, ret, etc.)
- [x] Snippets pour directives d'assembleur (.text, .data, .global, etc.)
- [x] Navigation avec Tab entre les placeholders
### Étape 4: Exécution dans QEMU
- [x] Commande de compilation avec riscv64-linux-gnu-gcc
- [x] Détection automatique du type de programme (_start vs main)
- [x] Options de compilation adaptées (standalone vs avec libc)
- [x] Commande d'exécution dans QEMU
- [x] Terminal intégré pour l'exécution
- [x] Boutons dans la barre d'outils
- [x] Support du clic droit et palette de commandes
### Étape 5: Debugging et Diagnostics
- [x] Validation syntaxique en temps réel
- [x] Vérification des instructions RISC-V
- [x] Validation des noms de registres
- [x] Détection des labels non définis
- [x] Vérification du nombre d'arguments des instructions
- [x] Warnings pour directives inconnues
- [x] Hover documentation pour instructions
- [x] Hover information pour registres
- [x] Hover documentation pour directives
- [x] IntelliSense avancé avec auto-complétion contextuelle
- [x] Complétion des instructions selon le contexte
- [x] Complétion des registres
- [x] Complétion des labels définis dans le document
- [x] Complétion des directives d'assembleur
- [x] Complétion des constantes système courantes
- [x] Navigation Go to Definition pour labels
- [x] Find All References pour labels
- [x] Configuration du debugging avec GDB
- [x] Support gdb-multiarch pour RISC-V
- [x] Compilation avec informations de debug (-g)
- [x] Configuration de debugging avec QEMU
- [x] Commandes de debugging intégrées
## Tâches en cours
- [ ] Tests des fonctionnalités de debugging
## Tâches futures possibles
### Étape 6: Templates et Boilerplate
- [ ] Commande "New RISC-V Program"
- [ ] Templates multiples (Hello World, calcul, tableaux)
- [ ] Snippets avancés (boucles, fonctions)
- [ ] Génération automatique de structure de base
### Étape 7: Intégration avancée
- [ ] Génération automatique de Makefile
- [ ] Support des projets multi-fichiers
- [ ] Outline view des labels et fonctions
- [ ] Folding avancé par sections
- [ ] Refactoring de base (renommage de labels)
### Étape 8: Émulation et Testing
- [ ] Terminal QEMU intégré dans VSCode
- [ ] Memory viewer pour inspection mémoire
- [ ] Register viewer en temps réel
- [ ] Step-by-step execution avec interface graphique
- [ ] Breakpoints visuels dans l'éditeur
### Améliorations techniques
- [ ] Support des macros assembleur
- [ ] Validation sémantique avancée
- [ ] Support des différentes variantes RISC-V (RV32, RV64)
- [ ] Support des extensions (M, A, F, D)
- [ ] Intégration avec simulateurs alternatifs
- [ ] Export vers différents formats (Intel HEX, etc.)
### Distribution et maintenance
- [ ] Tests automatisés
- [ ] Documentation utilisateur complète
- [ ] Empaquetage pour VS Code Marketplace
- [ ] CI/CD pour les releases
- [ ] Support multi-plateforme (Windows, macOS)
## Notes techniques
- Extension basée sur TypeScript
- Utilise l'API VSCode pour language servers
- Intégration avec toolchain GNU RISC-V
- Compatible avec QEMU user mode
- Support debugging via GDB multiarch

28
eslint.config.mjs Normal file
View File

@@ -0,0 +1,28 @@
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
export default [{
files: ["**/*.ts"],
}, {
plugins: {
"@typescript-eslint": typescriptEslint,
},
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
"@typescript-eslint/naming-convention": ["warn", {
selector: "import",
format: ["camelCase", "PascalCase"],
}],
curly: "warn",
eqeqeq: "warn",
"no-throw-literal": "warn",
semi: "warn",
},
}];

View File

@@ -0,0 +1,32 @@
{
"comments": {
"lineComment": "#",
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
"folding": {
"markers": {
"start": "^\\s*#\\s*region\\b",
"end": "^\\s*#\\s*endregion\\b"
}
},
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)"
}

3376
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

131
package.json Normal file
View File

@@ -0,0 +1,131 @@
{
"name": "riscv-asm",
"displayName": "RISC-V Assembly",
"description": "Support pour le développement en assembleur RISC-V",
"version": "0.0.1",
"engines": {
"vscode": "^1.101.0"
},
"categories": [
"Programming Languages",
"Other"
],
"activationEvents": [
"onLanguage:riscv-asm"
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "riscv-asm.helloWorld",
"title": "Hello World",
"category": "RISC-V"
},
{
"command": "riscv-asm.compile",
"title": "Compile RISC-V File",
"category": "RISC-V",
"icon": "$(tools)"
},
{
"command": "riscv-asm.run",
"title": "Run in QEMU",
"category": "RISC-V",
"icon": "$(play)"
},
{
"command": "riscv-asm.compileAndRun",
"title": "Compile & Run",
"category": "RISC-V",
"icon": "$(play-circle)"
}
],
"menus": {
"editor/title": [
{
"when": "resourceExtname == .s || resourceExtname == .asm || resourceExtname == .rv",
"command": "riscv-asm.compile",
"group": "navigation@1"
},
{
"when": "resourceExtname == .s || resourceExtname == .asm || resourceExtname == .rv",
"command": "riscv-asm.run",
"group": "navigation@2"
},
{
"when": "resourceExtname == .s || resourceExtname == .asm || resourceExtname == .rv",
"command": "riscv-asm.compileAndRun",
"group": "navigation@3"
}
],
"editor/context": [
{
"when": "resourceExtname == .s || resourceExtname == .asm || resourceExtname == .rv",
"command": "riscv-asm.compileAndRun",
"group": "risc-v"
}
],
"commandPalette": [
{
"command": "riscv-asm.compile",
"when": "resourceExtname == .s || resourceExtname == .asm || resourceExtname == .rv"
},
{
"command": "riscv-asm.run",
"when": "resourceExtname == .s || resourceExtname == .asm || resourceExtname == .rv"
},
{
"command": "riscv-asm.compileAndRun",
"when": "resourceExtname == .s || resourceExtname == .asm || resourceExtname == .rv"
}
]
},
"languages": [
{
"id": "riscv-asm",
"aliases": [
"RISC-V Assembly",
"riscv-asm"
],
"extensions": [
".s",
".asm",
".rv"
],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "riscv-asm",
"scopeName": "source.riscv-asm",
"path": "./syntaxes/riscv-asm.tmLanguage.json"
}
],
"snippets": [
{
"language": "riscv-asm",
"path": "./snippets/riscv.json"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint",
"lint": "eslint src",
"test": "vscode-test"
},
"devDependencies": {
"@types/vscode": "^1.101.0",
"@types/mocha": "^10.0.10",
"@types/node": "22.x",
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/parser": "^8.39.0",
"eslint": "^9.32.0",
"typescript": "^5.9.2",
"@vscode/test-cli": "^0.0.11",
"@vscode/test-electron": "^2.5.2"
}
}

289
snippets/riscv.json Normal file
View File

@@ -0,0 +1,289 @@
{
"Load Immediate": {
"prefix": "li",
"body": [
"li ${1:reg}, ${2:immediate}"
],
"description": "Load immediate value into register"
},
"Load Address": {
"prefix": "la",
"body": [
"la ${1:reg}, ${2:label}"
],
"description": "Load address of label into register"
},
"Add": {
"prefix": "add",
"body": [
"add ${1:rd}, ${2:rs1}, ${3:rs2}"
],
"description": "Add two registers"
},
"Add Immediate": {
"prefix": "addi",
"body": [
"addi ${1:rd}, ${2:rs1}, ${3:immediate}"
],
"description": "Add immediate to register"
},
"Subtract": {
"prefix": "sub",
"body": [
"sub ${1:rd}, ${2:rs1}, ${3:rs2}"
],
"description": "Subtract two registers"
},
"Move": {
"prefix": "mv",
"body": [
"mv ${1:rd}, ${2:rs}"
],
"description": "Move register (pseudo-instruction)"
},
"Load Word": {
"prefix": "lw",
"body": [
"lw ${1:rd}, ${2:offset}(${3:rs1})"
],
"description": "Load word from memory"
},
"Load Byte": {
"prefix": "lb",
"body": [
"lb ${1:rd}, ${2:offset}(${3:rs1})"
],
"description": "Load byte from memory"
},
"Load Half": {
"prefix": "lh",
"body": [
"lh ${1:rd}, ${2:offset}(${3:rs1})"
],
"description": "Load halfword from memory"
},
"Store Word": {
"prefix": "sw",
"body": [
"sw ${1:rs2}, ${2:offset}(${3:rs1})"
],
"description": "Store word to memory"
},
"Store Byte": {
"prefix": "sb",
"body": [
"sb ${1:rs2}, ${2:offset}(${3:rs1})"
],
"description": "Store byte to memory"
},
"Store Half": {
"prefix": "sh",
"body": [
"sh ${1:rs2}, ${2:offset}(${3:rs1})"
],
"description": "Store halfword to memory"
},
"Branch Equal": {
"prefix": "beq",
"body": [
"beq ${1:rs1}, ${2:rs2}, ${3:label}"
],
"description": "Branch if equal"
},
"Branch Not Equal": {
"prefix": "bne",
"body": [
"bne ${1:rs1}, ${2:rs2}, ${3:label}"
],
"description": "Branch if not equal"
},
"Branch Less Than": {
"prefix": "blt",
"body": [
"blt ${1:rs1}, ${2:rs2}, ${3:label}"
],
"description": "Branch if less than (signed)"
},
"Branch Greater Equal": {
"prefix": "bge",
"body": [
"bge ${1:rs1}, ${2:rs2}, ${3:label}"
],
"description": "Branch if greater or equal (signed)"
},
"Jump": {
"prefix": "j",
"body": [
"j ${1:label}"
],
"description": "Unconditional jump"
},
"Jump and Link": {
"prefix": "jal",
"body": [
"jal ${1:rd}, ${2:label}"
],
"description": "Jump and link"
},
"Jump and Link Register": {
"prefix": "jalr",
"body": [
"jalr ${1:rd}, ${2:rs1}, ${3:offset}"
],
"description": "Jump and link register"
},
"Environment Call": {
"prefix": "ecall",
"body": [
"ecall"
],
"description": "System call"
},
"No Operation": {
"prefix": "nop",
"body": [
"nop"
],
"description": "No operation"
},
"Return": {
"prefix": "ret",
"body": [
"ret"
],
"description": "Return from function"
},
"XOR": {
"prefix": "xor",
"body": [
"xor ${1:rd}, ${2:rs1}, ${3:rs2}"
],
"description": "Exclusive OR"
},
"XOR Immediate": {
"prefix": "xori",
"body": [
"xori ${1:rd}, ${2:rs1}, ${3:immediate}"
],
"description": "XOR with immediate"
},
"OR": {
"prefix": "or",
"body": [
"or ${1:rd}, ${2:rs1}, ${3:rs2}"
],
"description": "Bitwise OR"
},
"OR Immediate": {
"prefix": "ori",
"body": [
"ori ${1:rd}, ${2:rs1}, ${3:immediate}"
],
"description": "OR with immediate"
},
"AND": {
"prefix": "and",
"body": [
"and ${1:rd}, ${2:rs1}, ${3:rs2}"
],
"description": "Bitwise AND"
},
"AND Immediate": {
"prefix": "andi",
"body": [
"andi ${1:rd}, ${2:rs1}, ${3:immediate}"
],
"description": "AND with immediate"
},
"Shift Left Logical": {
"prefix": "sll",
"body": [
"sll ${1:rd}, ${2:rs1}, ${3:rs2}"
],
"description": "Shift left logical"
},
"Shift Left Logical Immediate": {
"prefix": "slli",
"body": [
"slli ${1:rd}, ${2:rs1}, ${3:shamt}"
],
"description": "Shift left logical immediate"
},
"Shift Right Logical": {
"prefix": "srl",
"body": [
"srl ${1:rd}, ${2:rs1}, ${3:rs2}"
],
"description": "Shift right logical"
},
"Shift Right Logical Immediate": {
"prefix": "srli",
"body": [
"srli ${1:rd}, ${2:rs1}, ${3:shamt}"
],
"description": "Shift right logical immediate"
},
"Text Section": {
"prefix": ".text",
"body": [
".text"
],
"description": "Text section directive"
},
"Data Section": {
"prefix": ".data",
"body": [
".data"
],
"description": "Data section directive"
},
"Global Symbol": {
"prefix": ".globl",
"body": [
".globl ${1:symbol}"
],
"description": "Make symbol globally visible"
},
"Word Data": {
"prefix": ".word",
"body": [
".word ${1:value}"
],
"description": "Define 32-bit word"
},
"Byte Data": {
"prefix": ".byte",
"body": [
".byte ${1:value}"
],
"description": "Define byte"
},
"String Data": {
"prefix": ".string",
"body": [
".string \"${1:text}\""
],
"description": "Define null-terminated string"
},
"ASCII String": {
"prefix": ".ascii",
"body": [
".ascii \"${1:text}\""
],
"description": "Define ASCII string (no null terminator)"
},
"Space Allocation": {
"prefix": ".space",
"body": [
".space ${1:bytes}"
],
"description": "Reserve space in bytes"
},
"Align": {
"prefix": ".align",
"body": [
".align ${1:boundary}"
],
"description": "Align to boundary"
}
}

264
src/completion.ts Normal file
View File

@@ -0,0 +1,264 @@
import * as vscode from 'vscode';
// Instructions avec leurs signatures
const INSTRUCTION_COMPLETIONS: { [key: string]: vscode.CompletionItem } = {};
// Initialiser les completions d'instructions
function initializeInstructionCompletions() {
const instructions = [
// Arithmétiques
{ name: 'add', detail: 'Add two registers', signature: 'add rd, rs1, rs2', kind: vscode.CompletionItemKind.Function },
{ name: 'addi', detail: 'Add immediate', signature: 'addi rd, rs1, imm', kind: vscode.CompletionItemKind.Function },
{ name: 'sub', detail: 'Subtract', signature: 'sub rd, rs1, rs2', kind: vscode.CompletionItemKind.Function },
{ name: 'lui', detail: 'Load upper immediate', signature: 'lui rd, imm', kind: vscode.CompletionItemKind.Function },
{ name: 'auipc', detail: 'Add upper immediate to PC', signature: 'auipc rd, imm', kind: vscode.CompletionItemKind.Function },
// Logiques
{ name: 'xor', detail: 'Exclusive OR', signature: 'xor rd, rs1, rs2', kind: vscode.CompletionItemKind.Function },
{ name: 'xori', detail: 'XOR immediate', signature: 'xori rd, rs1, imm', kind: vscode.CompletionItemKind.Function },
{ name: 'or', detail: 'Bitwise OR', signature: 'or rd, rs1, rs2', kind: vscode.CompletionItemKind.Function },
{ name: 'ori', detail: 'OR immediate', signature: 'ori rd, rs1, imm', kind: vscode.CompletionItemKind.Function },
{ name: 'and', detail: 'Bitwise AND', signature: 'and rd, rs1, rs2', kind: vscode.CompletionItemKind.Function },
{ name: 'andi', detail: 'AND immediate', signature: 'andi rd, rs1, imm', kind: vscode.CompletionItemKind.Function },
// Décalages
{ name: 'sll', detail: 'Shift left logical', signature: 'sll rd, rs1, rs2', kind: vscode.CompletionItemKind.Function },
{ name: 'slli', detail: 'Shift left logical immediate', signature: 'slli rd, rs1, shamt', kind: vscode.CompletionItemKind.Function },
{ name: 'srl', detail: 'Shift right logical', signature: 'srl rd, rs1, rs2', kind: vscode.CompletionItemKind.Function },
{ name: 'srli', detail: 'Shift right logical immediate', signature: 'srli rd, rs1, shamt', kind: vscode.CompletionItemKind.Function },
{ name: 'sra', detail: 'Shift right arithmetic', signature: 'sra rd, rs1, rs2', kind: vscode.CompletionItemKind.Function },
{ name: 'srai', detail: 'Shift right arithmetic immediate', signature: 'srai rd, rs1, shamt', kind: vscode.CompletionItemKind.Function },
// Comparaisons
{ name: 'slt', detail: 'Set less than', signature: 'slt rd, rs1, rs2', kind: vscode.CompletionItemKind.Function },
{ name: 'slti', detail: 'Set less than immediate', signature: 'slti rd, rs1, imm', kind: vscode.CompletionItemKind.Function },
{ name: 'sltu', detail: 'Set less than unsigned', signature: 'sltu rd, rs1, rs2', kind: vscode.CompletionItemKind.Function },
{ name: 'sltiu', detail: 'Set less than immediate unsigned', signature: 'sltiu rd, rs1, imm', kind: vscode.CompletionItemKind.Function },
// Mémoire
{ name: 'lb', detail: 'Load byte', signature: 'lb rd, offset(rs1)', kind: vscode.CompletionItemKind.Function },
{ name: 'lh', detail: 'Load halfword', signature: 'lh rd, offset(rs1)', kind: vscode.CompletionItemKind.Function },
{ name: 'lw', detail: 'Load word', signature: 'lw rd, offset(rs1)', kind: vscode.CompletionItemKind.Function },
{ name: 'ld', detail: 'Load doubleword', signature: 'ld rd, offset(rs1)', kind: vscode.CompletionItemKind.Function },
{ name: 'lbu', detail: 'Load byte unsigned', signature: 'lbu rd, offset(rs1)', kind: vscode.CompletionItemKind.Function },
{ name: 'lhu', detail: 'Load halfword unsigned', signature: 'lhu rd, offset(rs1)', kind: vscode.CompletionItemKind.Function },
{ name: 'lwu', detail: 'Load word unsigned', signature: 'lwu rd, offset(rs1)', kind: vscode.CompletionItemKind.Function },
{ name: 'sb', detail: 'Store byte', signature: 'sb rs2, offset(rs1)', kind: vscode.CompletionItemKind.Function },
{ name: 'sh', detail: 'Store halfword', signature: 'sh rs2, offset(rs1)', kind: vscode.CompletionItemKind.Function },
{ name: 'sw', detail: 'Store word', signature: 'sw rs2, offset(rs1)', kind: vscode.CompletionItemKind.Function },
{ name: 'sd', detail: 'Store doubleword', signature: 'sd rs2, offset(rs1)', kind: vscode.CompletionItemKind.Function },
// Branchements
{ name: 'beq', detail: 'Branch if equal', signature: 'beq rs1, rs2, label', kind: vscode.CompletionItemKind.Function },
{ name: 'bne', detail: 'Branch if not equal', signature: 'bne rs1, rs2, label', kind: vscode.CompletionItemKind.Function },
{ name: 'blt', detail: 'Branch if less than', signature: 'blt rs1, rs2, label', kind: vscode.CompletionItemKind.Function },
{ name: 'bge', detail: 'Branch if greater equal', signature: 'bge rs1, rs2, label', kind: vscode.CompletionItemKind.Function },
{ name: 'bltu', detail: 'Branch if less than unsigned', signature: 'bltu rs1, rs2, label', kind: vscode.CompletionItemKind.Function },
{ name: 'bgeu', detail: 'Branch if greater equal unsigned', signature: 'bgeu rs1, rs2, label', kind: vscode.CompletionItemKind.Function },
{ name: 'jal', detail: 'Jump and link', signature: 'jal rd, label', kind: vscode.CompletionItemKind.Function },
{ name: 'jalr', detail: 'Jump and link register', signature: 'jalr rd, rs1, offset', kind: vscode.CompletionItemKind.Function },
// Pseudo-instructions
{ name: 'nop', detail: 'No operation', signature: 'nop', kind: vscode.CompletionItemKind.Function },
{ name: 'li', detail: 'Load immediate', signature: 'li rd, imm', kind: vscode.CompletionItemKind.Function },
{ name: 'la', detail: 'Load address', signature: 'la rd, label', kind: vscode.CompletionItemKind.Function },
{ name: 'mv', detail: 'Move register', signature: 'mv rd, rs', kind: vscode.CompletionItemKind.Function },
{ name: 'not', detail: 'Bitwise NOT', signature: 'not rd, rs', kind: vscode.CompletionItemKind.Function },
{ name: 'neg', detail: 'Negate', signature: 'neg rd, rs', kind: vscode.CompletionItemKind.Function },
{ name: 'j', detail: 'Jump', signature: 'j label', kind: vscode.CompletionItemKind.Function },
{ name: 'jr', detail: 'Jump register', signature: 'jr rs', kind: vscode.CompletionItemKind.Function },
{ name: 'ret', detail: 'Return', signature: 'ret', kind: vscode.CompletionItemKind.Function },
{ name: 'call', detail: 'Call function', signature: 'call label', kind: vscode.CompletionItemKind.Function },
// Système
{ name: 'ecall', detail: 'Environment call', signature: 'ecall', kind: vscode.CompletionItemKind.Function },
{ name: 'ebreak', detail: 'Environment break', signature: 'ebreak', kind: vscode.CompletionItemKind.Function }
];
instructions.forEach(instr => {
const item = new vscode.CompletionItem(instr.name, instr.kind);
item.detail = instr.detail;
item.documentation = new vscode.MarkdownString().appendCodeblock(instr.signature, 'riscv-asm');
item.insertText = instr.name;
INSTRUCTION_COMPLETIONS[instr.name] = item;
});
}
// Créer les registres avec auto-complétion
function createRegisterCompletions(): vscode.CompletionItem[] {
const completions: vscode.CompletionItem[] = [];
// Registres temporaires
for (let i = 0; i < 7; i++) {
const item = new vscode.CompletionItem(`t${i}`, vscode.CompletionItemKind.Variable);
item.detail = `Temporary register ${i} (x${i + 5})`;
completions.push(item);
}
// Registres sauvegardés
for (let i = 0; i < 12; i++) {
const item = new vscode.CompletionItem(`s${i}`, vscode.CompletionItemKind.Variable);
item.detail = `Saved register ${i} (x${i + 8})`;
completions.push(item);
}
// Registres d'arguments
for (let i = 0; i < 8; i++) {
const item = new vscode.CompletionItem(`a${i}`, vscode.CompletionItemKind.Variable);
item.detail = `Argument register ${i} (x${i + 10})`;
completions.push(item);
}
// Registres spéciaux
const specialRegs = [
{ name: 'zero', detail: 'Always zero (x0)' },
{ name: 'ra', detail: 'Return address (x1)' },
{ name: 'sp', detail: 'Stack pointer (x2)' },
{ name: 'gp', detail: 'Global pointer (x3)' },
{ name: 'tp', detail: 'Thread pointer (x4)' },
{ name: 'fp', detail: 'Frame pointer (x8/s0)' }
];
specialRegs.forEach(reg => {
const item = new vscode.CompletionItem(reg.name, vscode.CompletionItemKind.Variable);
item.detail = reg.detail;
completions.push(item);
});
return completions;
}
const REGISTER_COMPLETIONS = createRegisterCompletions();
export class RiscvCompletionProvider implements vscode.CompletionItemProvider {
constructor() {
initializeInstructionCompletions();
}
provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
context: vscode.CompletionContext
): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
const line = document.lineAt(position).text;
const linePrefix = line.substring(0, position.character);
// Collecter tous les labels définis dans le document
const labels = this.collectLabels(document);
// Détecter le contexte de completion
const completionItems: vscode.CompletionItem[] = [];
// Si on commence la ligne, proposer instructions + directives + labels
if (linePrefix.trim() === '' || linePrefix.match(/^\s+$/)) {
// Instructions
completionItems.push(...Object.values(INSTRUCTION_COMPLETIONS));
// Directives
completionItems.push(...this.getDirectiveCompletions());
// Labels (pour les sauts)
completionItems.push(...labels);
}
// Si on est après une instruction, proposer registres + labels + constantes
else {
const instructionMatch = linePrefix.match(/^\s*(\w+)\s+/);
if (instructionMatch) {
const instruction = instructionMatch[1].toLowerCase();
// Registres toujours disponibles
completionItems.push(...REGISTER_COMPLETIONS);
// Labels pour instructions de branchement/saut
if (['beq', 'bne', 'blt', 'bge', 'bltu', 'bgeu', 'jal', 'j', 'call'].includes(instruction)) {
completionItems.push(...labels);
}
// Constantes système courantes
completionItems.push(...this.getSystemConstantCompletions());
}
}
return completionItems;
}
private collectLabels(document: vscode.TextDocument): vscode.CompletionItem[] {
const labels: vscode.CompletionItem[] = [];
const text = document.getText();
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
const labelMatch = line.match(/^(\w+):\s*/);
if (labelMatch) {
const labelName = labelMatch[1];
const item = new vscode.CompletionItem(labelName, vscode.CompletionItemKind.Reference);
item.detail = `Label defined at line ${i + 1}`;
item.documentation = `Jump target: ${labelName}`;
labels.push(item);
}
}
return labels;
}
private getDirectiveCompletions(): vscode.CompletionItem[] {
const directives = [
{ name: '.text', detail: 'Text section', doc: 'Start of code section' },
{ name: '.data', detail: 'Data section', doc: 'Start of data section' },
{ name: '.bss', detail: 'BSS section', doc: 'Uninitialized data section' },
{ name: '.global', detail: 'Global symbol', doc: 'Make symbol globally visible' },
{ name: '.globl', detail: 'Global symbol', doc: 'Make symbol globally visible (GNU syntax)' },
{ name: '.word', detail: 'Define word', doc: 'Define 32-bit word' },
{ name: '.dword', detail: 'Define doubleword', doc: 'Define 64-bit doubleword' },
{ name: '.byte', detail: 'Define byte', doc: 'Define 8-bit byte' },
{ name: '.half', detail: 'Define halfword', doc: 'Define 16-bit halfword' },
{ name: '.string', detail: 'Define string', doc: 'Define null-terminated string' },
{ name: '.ascii', detail: 'Define ASCII', doc: 'Define ASCII string (no null terminator)' },
{ name: '.space', detail: 'Reserve space', doc: 'Reserve specified bytes' },
{ name: '.align', detail: 'Align data', doc: 'Align to boundary' },
{ name: '.equ', detail: 'Define constant', doc: 'Define symbolic constant' },
{ name: '.set', detail: 'Set value', doc: 'Set symbol value' }
];
return directives.map(dir => {
const item = new vscode.CompletionItem(dir.name, vscode.CompletionItemKind.Keyword);
item.detail = dir.detail;
item.documentation = dir.doc;
item.insertText = dir.name;
return item;
});
}
private getSystemConstantCompletions(): vscode.CompletionItem[] {
const constants = [
// Codes d'appel système Linux
{ name: '64', detail: 'sys_write', doc: 'Write system call number' },
{ name: '63', detail: 'sys_read', doc: 'Read system call number' },
{ name: '93', detail: 'sys_exit', doc: 'Exit system call number' },
{ name: '1024', detail: 'sys_open', doc: 'Open file system call number' },
{ name: '1025', detail: 'sys_close', doc: 'Close file system call number' },
// File descriptors
{ name: '0', detail: 'STDIN', doc: 'Standard input file descriptor' },
{ name: '1', detail: 'STDOUT', doc: 'Standard output file descriptor' },
{ name: '2', detail: 'STDERR', doc: 'Standard error file descriptor' },
// Valeurs courantes
{ name: '42', detail: 'The answer', doc: 'Common test value' }
];
return constants.map(constant => {
const item = new vscode.CompletionItem(constant.name, vscode.CompletionItemKind.Constant);
item.detail = constant.detail;
item.documentation = constant.doc;
return item;
});
}
resolveCompletionItem(item: vscode.CompletionItem, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CompletionItem> {
return item;
}
}

78
src/definition.ts Normal file
View File

@@ -0,0 +1,78 @@
import * as vscode from 'vscode';
export class RiscvDefinitionProvider implements vscode.DefinitionProvider {
provideDefinition(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken
): vscode.ProviderResult<vscode.Definition | vscode.LocationLink[]> {
const range = document.getWordRangeAtPosition(position);
if (!range) {
return undefined;
}
const word = document.getText(range);
// Chercher la définition du label dans le document
const labelDefinition = this.findLabelDefinition(document, word);
if (labelDefinition) {
return new vscode.Location(document.uri, labelDefinition);
}
return undefined;
}
private findLabelDefinition(document: vscode.TextDocument, labelName: string): vscode.Position | undefined {
const text = document.getText();
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
const labelMatch = line.match(/^(\w+):\s*/);
if (labelMatch && labelMatch[1] === labelName) {
return new vscode.Position(i, 0);
}
}
return undefined;
}
}
export class RiscvReferenceProvider implements vscode.ReferenceProvider {
provideReferences(
document: vscode.TextDocument,
position: vscode.Position,
context: vscode.ReferenceContext,
token: vscode.CancellationToken
): vscode.ProviderResult<vscode.Location[]> {
const range = document.getWordRangeAtPosition(position);
if (!range) {
return [];
}
const word = document.getText(range);
const references: vscode.Location[] = [];
// Chercher toutes les références au label
const text = document.getText();
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Chercher les occurrences du label
let match;
const regex = new RegExp(`\\b${word}\\b`, 'g');
while ((match = regex.exec(line)) !== null) {
const startPos = new vscode.Position(i, match.index);
const endPos = new vscode.Position(i, match.index + word.length);
const location = new vscode.Location(document.uri, new vscode.Range(startPos, endPos));
references.push(location);
}
}
return references;
}
}

215
src/diagnostics.ts Normal file
View File

@@ -0,0 +1,215 @@
import * as vscode from 'vscode';
// Instructions RISC-V valides
const VALID_INSTRUCTIONS = new Set([
// Instructions arithmétiques
'add', 'addi', 'sub', 'lui', 'auipc',
'xor', 'xori', 'or', 'ori', 'and', 'andi',
'sll', 'slli', 'srl', 'srli', 'sra', 'srai',
'slt', 'slti', 'sltu', 'sltiu',
// Instructions de chargement/stockage
'lb', 'lh', 'lw', 'ld', 'lbu', 'lhu', 'lwu',
'sb', 'sh', 'sw', 'sd',
// Instructions de branchement
'beq', 'bne', 'blt', 'bge', 'bltu', 'bgeu',
'jal', 'jalr',
// Instructions système
'ecall', 'ebreak', 'fence', 'fence.i',
'csrrw', 'csrrs', 'csrrc', 'csrrwi', 'csrrsi', 'csrrci',
// Instructions de multiplication/division
'mul', 'mulh', 'mulhsu', 'mulhu', 'div', 'divu', 'rem', 'remu',
// Pseudo-instructions
'nop', 'li', 'la', 'mv', 'not', 'neg', 'seqz', 'snez',
'sltz', 'sgtz', 'j', 'jr', 'ret', 'call', 'tail'
]);
// Registres RISC-V valides
const VALID_REGISTERS = new Set([
// Registres numériques
'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9',
'x10', 'x11', 'x12', 'x13', 'x14', 'x15', 'x16', 'x17', 'x18', 'x19',
'x20', 'x21', 'x22', 'x23', 'x24', 'x25', 'x26', 'x27', 'x28', 'x29',
'x30', 'x31',
// Noms ABI
'zero', 'ra', 'sp', 'gp', 'tp',
't0', 't1', 't2', 't3', 't4', 't5', 't6',
's0', 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11',
'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7',
'fp',
// Registres flottants
'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9',
'f10', 'f11', 'f12', 'f13', 'f14', 'f15', 'f16', 'f17', 'f18', 'f19',
'f20', 'f21', 'f22', 'f23', 'f24', 'f25', 'f26', 'f27', 'f28', 'f29',
'f30', 'f31',
'ft0', 'ft1', 'ft2', 'ft3', 'ft4', 'ft5', 'ft6', 'ft7', 'ft8', 'ft9', 'ft10', 'ft11',
'fs0', 'fs1', 'fs2', 'fs3', 'fs4', 'fs5', 'fs6', 'fs7', 'fs8', 'fs9', 'fs10', 'fs11',
'fa0', 'fa1', 'fa2', 'fa3', 'fa4', 'fa5', 'fa6', 'fa7'
]);
// Directives valides
const VALID_DIRECTIVES = new Set([
'.text', '.data', '.bss', '.section', '.global', '.globl', '.extern',
'.equ', '.set', '.align', '.balign', '.word', '.dword', '.byte', '.half',
'.ascii', '.asciiz', '.string', '.zero', '.space', '.skip', '.org', '.include'
]);
export function validateRiscvDocument(document: vscode.TextDocument): vscode.Diagnostic[] {
const diagnostics: vscode.Diagnostic[] = [];
const text = document.getText();
const lines = text.split('\n');
const labels = new Set<string>();
const usedLabels = new Set<string>();
for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {
const line = lines[lineNumber].trim();
// Ignorer les lignes vides et les commentaires
if (!line || line.startsWith('#')) {
continue;
}
// Détecter les labels
const labelMatch = line.match(/^(\w+):\s*(.*)/);
if (labelMatch) {
const labelName = labelMatch[1];
labels.add(labelName);
// Vérifier la ligne après le label
const restOfLine = labelMatch[2].trim();
if (restOfLine) {
validateInstruction(restOfLine, lineNumber, diagnostics, usedLabels);
}
continue;
}
// Vérifier les instructions
validateInstruction(line, lineNumber, diagnostics, usedLabels);
}
// Vérifier les labels non définis
for (const usedLabel of usedLabels) {
if (!labels.has(usedLabel)) {
// Trouver où ce label est utilisé pour signaler l'erreur
for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {
const line = lines[lineNumber];
if (line.includes(usedLabel)) {
const range = new vscode.Range(lineNumber, 0, lineNumber, line.length);
const diagnostic = new vscode.Diagnostic(
range,
`Undefined label: ${usedLabel}`,
vscode.DiagnosticSeverity.Error
);
diagnostic.code = 'undefined-label';
diagnostics.push(diagnostic);
break;
}
}
}
}
return diagnostics;
}
function validateInstruction(line: string, lineNumber: number, diagnostics: vscode.Diagnostic[], usedLabels: Set<string>) {
// Ignorer les directives d'assembleur
if (line.startsWith('.')) {
const directive = line.split(/\s+/)[0].toLowerCase();
if (!VALID_DIRECTIVES.has(directive)) {
const range = new vscode.Range(lineNumber, 0, lineNumber, directive.length);
const diagnostic = new vscode.Diagnostic(
range,
`Unknown directive: ${directive}`,
vscode.DiagnosticSeverity.Warning
);
diagnostic.code = 'unknown-directive';
diagnostics.push(diagnostic);
}
return;
}
// Parser l'instruction
const parts = line.split(/[\s,()]+/).filter(part => part.length > 0);
if (parts.length === 0) return;
const instruction = parts[0].toLowerCase();
// Vérifier si l'instruction est valide
if (!VALID_INSTRUCTIONS.has(instruction)) {
const range = new vscode.Range(lineNumber, 0, lineNumber, instruction.length);
const diagnostic = new vscode.Diagnostic(
range,
`Unknown instruction: ${instruction}`,
vscode.DiagnosticSeverity.Error
);
diagnostic.code = 'unknown-instruction';
diagnostics.push(diagnostic);
return;
}
// Vérifier les arguments
const args = parts.slice(1);
validateInstructionArgs(instruction, args, lineNumber, diagnostics, usedLabels);
}
function validateInstructionArgs(instruction: string, args: string[], lineNumber: number, diagnostics: vscode.Diagnostic[], usedLabels: Set<string>) {
// Vérifier le nombre d'arguments pour certaines instructions
const expectedArgCounts: { [key: string]: number } = {
'add': 3, 'sub': 3, 'xor': 3, 'or': 3, 'and': 3,
'addi': 3, 'xori': 3, 'ori': 3, 'andi': 3,
'beq': 3, 'bne': 3, 'blt': 3, 'bge': 3,
'lw': 2, 'sw': 2, 'lb': 2, 'sb': 2,
'li': 2, 'mv': 2, 'j': 1, 'jal': 2,
'ecall': 0, 'ret': 0, 'nop': 0
};
if (expectedArgCounts[instruction] !== undefined) {
const expected = expectedArgCounts[instruction];
if (args.length !== expected) {
const range = new vscode.Range(lineNumber, 0, lineNumber, 100);
const diagnostic = new vscode.Diagnostic(
range,
`${instruction} expects ${expected} arguments, got ${args.length}`,
vscode.DiagnosticSeverity.Error
);
diagnostic.code = 'wrong-arg-count';
diagnostics.push(diagnostic);
return;
}
}
// Vérifier les registres et collecter les labels utilisés
for (const arg of args) {
const cleanArg = arg.replace(/[(),]/g, '');
// Ignorer les nombres (décimaux, hex, etc.)
if (/^-?\d+$/.test(cleanArg) || /^0x[0-9a-fA-F]+$/.test(cleanArg)) {
continue;
}
// Vérifier si c'est un registre
if (cleanArg.startsWith('x') || VALID_REGISTERS.has(cleanArg.toLowerCase())) {
if (!VALID_REGISTERS.has(cleanArg.toLowerCase())) {
const range = new vscode.Range(lineNumber, 0, lineNumber, 100);
const diagnostic = new vscode.Diagnostic(
range,
`Invalid register: ${cleanArg}`,
vscode.DiagnosticSeverity.Error
);
diagnostic.code = 'invalid-register';
diagnostics.push(diagnostic);
}
}
// Sinon, c'est probablement un label
else if (/^[a-zA-Z_]\w*$/.test(cleanArg)) {
usedLabels.add(cleanArg);
}
}
}

213
src/extension.ts Normal file
View File

@@ -0,0 +1,213 @@
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { execSync, spawn } from 'child_process';
import { validateRiscvDocument } from './diagnostics';
import { RiscvHoverProvider } from './hover';
import { RiscvCompletionProvider } from './completion';
import { RiscvDefinitionProvider, RiscvReferenceProvider } from './definition';
export function activate(context: vscode.ExtensionContext) {
console.log('RISC-V Assembly extension is now active!');
// Créer une collection de diagnostics
const diagnosticCollection = vscode.languages.createDiagnosticCollection('riscv-asm');
context.subscriptions.push(diagnosticCollection);
// Enregistrer le hover provider
const hoverProvider = vscode.languages.registerHoverProvider('riscv-asm', new RiscvHoverProvider());
context.subscriptions.push(hoverProvider);
// Enregistrer le completion provider
const completionProvider = vscode.languages.registerCompletionItemProvider(
'riscv-asm',
new RiscvCompletionProvider(),
'.', // Trigger sur '.' pour les directives
',' // Trigger sur ',' pour les arguments
);
context.subscriptions.push(completionProvider);
// Enregistrer les providers de navigation
const definitionProvider = vscode.languages.registerDefinitionProvider('riscv-asm', new RiscvDefinitionProvider());
const referenceProvider = vscode.languages.registerReferenceProvider('riscv-asm', new RiscvReferenceProvider());
context.subscriptions.push(definitionProvider, referenceProvider);
// Fonction pour mettre à jour les diagnostics
function updateDiagnostics(document: vscode.TextDocument) {
if (document.languageId === 'riscv-asm') {
const diagnostics = validateRiscvDocument(document);
diagnosticCollection.set(document.uri, diagnostics);
}
}
// Écouter les changements de document
context.subscriptions.push(
vscode.workspace.onDidChangeTextDocument((event) => {
updateDiagnostics(event.document);
}),
vscode.workspace.onDidOpenTextDocument((document) => {
updateDiagnostics(document);
}),
vscode.workspace.onDidCloseTextDocument((document) => {
diagnosticCollection.delete(document.uri);
})
);
// Valider tous les documents déjà ouverts
vscode.workspace.textDocuments.forEach(updateDiagnostics);
// Commande Hello World existante
const helloWorldDisposable = vscode.commands.registerCommand('riscv-asm.helloWorld', () => {
vscode.window.showInformationMessage('Hello World from RISC-V Assembly!');
});
// Commande de compilation
const compileDisposable = vscode.commands.registerCommand('riscv-asm.compile', async () => {
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
vscode.window.showErrorMessage('No active file to compile');
return;
}
const document = activeEditor.document;
if (!document.fileName.match(/\.(s|asm|rv)$/)) {
vscode.window.showErrorMessage('Please compile a RISC-V assembly file (.s, .asm, .rv)');
return;
}
// Sauvegarder le fichier avant compilation
await document.save();
try {
await compileFile(document.fileName);
vscode.window.showInformationMessage('Compilation successful!');
} catch (error) {
vscode.window.showErrorMessage(`Compilation failed: ${error}`);
}
});
// Commande d'exécution avec QEMU
const runDisposable = vscode.commands.registerCommand('riscv-asm.run', async () => {
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
vscode.window.showErrorMessage('No active file to run');
return;
}
const document = activeEditor.document;
if (!document.fileName.match(/\.(s|asm|rv)$/)) {
vscode.window.showErrorMessage('Please run a RISC-V assembly file (.s, .asm, .rv)');
return;
}
// Sauvegarder le fichier avant compilation et exécution
await document.save();
try {
// D'abord compiler
await compileFile(document.fileName);
// Puis exécuter
await runInQemu(document.fileName);
} catch (error) {
vscode.window.showErrorMessage(`Execution failed: ${error}`);
}
});
// Commande de compilation et exécution en une fois
const compileAndRunDisposable = vscode.commands.registerCommand('riscv-asm.compileAndRun', async () => {
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
vscode.window.showErrorMessage('No active file');
return;
}
const document = activeEditor.document;
if (!document.fileName.match(/\.(s|asm|rv)$/)) {
vscode.window.showErrorMessage('Please use a RISC-V assembly file (.s, .asm, .rv)');
return;
}
await document.save();
try {
vscode.window.showInformationMessage('Compiling and running...');
await compileFile(document.fileName);
await runInQemu(document.fileName);
} catch (error) {
vscode.window.showErrorMessage(`Failed: ${error}`);
}
});
context.subscriptions.push(
helloWorldDisposable,
compileDisposable,
runDisposable,
compileAndRunDisposable
);
}
async function compileFile(sourceFile: string): Promise<void> {
return new Promise((resolve, reject) => {
const dir = path.dirname(sourceFile);
const baseName = path.basename(sourceFile, path.extname(sourceFile));
const outputFile = path.join(dir, baseName);
// Lire le contenu pour détecter le type de programme
const content = fs.readFileSync(sourceFile, 'utf8');
const hasStart = content.includes('_start');
const hasMain = content.includes('main:');
let compileCommand: string;
if (hasStart && !hasMain) {
// Programme standalone avec _start
compileCommand = `riscv64-linux-gnu-gcc -nostdlib -nostartfiles -static -o "${outputFile}" "${sourceFile}"`;
} else {
// Programme avec main ou standard
compileCommand = `riscv64-linux-gnu-gcc -static -o "${outputFile}" "${sourceFile}"`;
}
try {
execSync(compileCommand, {
cwd: dir,
stdio: ['pipe', 'pipe', 'pipe']
});
resolve();
} catch (error: any) {
reject(`Compilation error: ${error.stderr?.toString() || error.message}`);
}
});
}
async function runInQemu(sourceFile: string): Promise<void> {
return new Promise((resolve, reject) => {
const dir = path.dirname(sourceFile);
const baseName = path.basename(sourceFile, path.extname(sourceFile));
const executableFile = path.join(dir, baseName);
// Vérifier que l'exécutable existe
if (!fs.existsSync(executableFile)) {
reject('Executable not found. Please compile first.');
return;
}
// Créer un terminal et exécuter avec QEMU
const terminal = vscode.window.createTerminal({
name: 'RISC-V QEMU',
cwd: dir
});
terminal.show();
terminal.sendText(`qemu-riscv64 -L /usr/riscv64-linux-gnu "${executableFile}"`);
// Afficher un message d'information
vscode.window.showInformationMessage(`Running ${baseName} in QEMU terminal`);
resolve();
});
}
export function deactivate() {
console.log('RISC-V Assembly extension deactivated');
}

236
src/hover.ts Normal file
View File

@@ -0,0 +1,236 @@
import * as vscode from 'vscode';
// Documentation des instructions RISC-V
const INSTRUCTION_DOCS: { [key: string]: { description: string; syntax: string; example: string } } = {
'add': {
description: 'Add two registers',
syntax: 'add rd, rs1, rs2',
example: 'add t0, t1, t2 # t0 = t1 + t2'
},
'addi': {
description: 'Add immediate to register',
syntax: 'addi rd, rs1, imm',
example: 'addi t0, t1, 10 # t0 = t1 + 10'
},
'sub': {
description: 'Subtract two registers',
syntax: 'sub rd, rs1, rs2',
example: 'sub t0, t1, t2 # t0 = t1 - t2'
},
'li': {
description: 'Load immediate value (pseudo-instruction)',
syntax: 'li rd, imm',
example: 'li t0, 42 # t0 = 42'
},
'la': {
description: 'Load address (pseudo-instruction)',
syntax: 'la rd, label',
example: 'la t0, msg # t0 = address of msg'
},
'mv': {
description: 'Move register (pseudo-instruction)',
syntax: 'mv rd, rs',
example: 'mv t0, t1 # t0 = t1'
},
'lw': {
description: 'Load word from memory',
syntax: 'lw rd, offset(rs1)',
example: 'lw t0, 8(sp) # t0 = memory[sp + 8]'
},
'sw': {
description: 'Store word to memory',
syntax: 'sw rs2, offset(rs1)',
example: 'sw t0, 8(sp) # memory[sp + 8] = t0'
},
'lb': {
description: 'Load byte from memory (sign-extended)',
syntax: 'lb rd, offset(rs1)',
example: 'lb t0, 0(t1) # t0 = (signed) memory[t1]'
},
'sb': {
description: 'Store byte to memory',
syntax: 'sb rs2, offset(rs1)',
example: 'sb t0, 0(t1) # memory[t1] = t0[7:0]'
},
'beq': {
description: 'Branch if equal',
syntax: 'beq rs1, rs2, label',
example: 'beq t0, t1, loop # if t0 == t1 goto loop'
},
'bne': {
description: 'Branch if not equal',
syntax: 'bne rs1, rs2, label',
example: 'bne t0, zero, skip # if t0 != 0 goto skip'
},
'blt': {
description: 'Branch if less than (signed)',
syntax: 'blt rs1, rs2, label',
example: 'blt t0, t1, smaller # if t0 < t1 goto smaller'
},
'bge': {
description: 'Branch if greater or equal (signed)',
syntax: 'bge rs1, rs2, label',
example: 'bge t0, t1, bigger # if t0 >= t1 goto bigger'
},
'j': {
description: 'Unconditional jump (pseudo-instruction)',
syntax: 'j label',
example: 'j loop # goto loop'
},
'jal': {
description: 'Jump and link',
syntax: 'jal rd, label',
example: 'jal ra, function # ra = PC+4, goto function'
},
'jalr': {
description: 'Jump and link register',
syntax: 'jalr rd, rs1, offset',
example: 'jalr ra, t0, 0 # ra = PC+4, goto t0'
},
'ret': {
description: 'Return from function (pseudo-instruction)',
syntax: 'ret',
example: 'ret # goto ra (return address)'
},
'ecall': {
description: 'Environment call (system call)',
syntax: 'ecall',
example: 'ecall # invoke system call'
},
'nop': {
description: 'No operation (pseudo-instruction)',
syntax: 'nop',
example: 'nop # do nothing'
},
'xor': {
description: 'Exclusive OR',
syntax: 'xor rd, rs1, rs2',
example: 'xor t0, t1, t2 # t0 = t1 ^ t2'
},
'or': {
description: 'Bitwise OR',
syntax: 'or rd, rs1, rs2',
example: 'or t0, t1, t2 # t0 = t1 | t2'
},
'and': {
description: 'Bitwise AND',
syntax: 'and rd, rs1, rs2',
example: 'and t0, t1, t2 # t0 = t1 & t2'
},
'sll': {
description: 'Shift left logical',
syntax: 'sll rd, rs1, rs2',
example: 'sll t0, t1, t2 # t0 = t1 << t2'
},
'srl': {
description: 'Shift right logical',
syntax: 'srl rd, rs1, rs2',
example: 'srl t0, t1, t2 # t0 = t1 >> t2 (unsigned)'
},
'sra': {
description: 'Shift right arithmetic',
syntax: 'sra rd, rs1, rs2',
example: 'sra t0, t1, t2 # t0 = t1 >> t2 (signed)'
}
};
// Documentation des registres
const REGISTER_DOCS: { [key: string]: { description: string; abi_name?: string } } = {
'zero': { description: 'Always zero (x0)' },
'ra': { description: 'Return address (x1)' },
'sp': { description: 'Stack pointer (x2)' },
'gp': { description: 'Global pointer (x3)' },
'tp': { description: 'Thread pointer (x4)' },
't0': { description: 'Temporary register 0 (x5)' },
't1': { description: 'Temporary register 1 (x6)' },
't2': { description: 'Temporary register 2 (x7)' },
's0': { description: 'Saved register 0 / Frame pointer (x8)', abi_name: 'fp' },
's1': { description: 'Saved register 1 (x9)' },
'a0': { description: 'Argument/return value 0 (x10)' },
'a1': { description: 'Argument/return value 1 (x11)' },
'a2': { description: 'Argument register 2 (x12)' },
'a3': { description: 'Argument register 3 (x13)' },
'a4': { description: 'Argument register 4 (x14)' },
'a5': { description: 'Argument register 5 (x15)' },
'a6': { description: 'Argument register 6 (x16)' },
'a7': { description: 'Argument register 7 (x17)' }
};
// Documentation des directives
const DIRECTIVE_DOCS: { [key: string]: { description: string; syntax: string; example: string } } = {
'.text': {
description: 'Start text (code) section',
syntax: '.text',
example: '.text # Code goes here'
},
'.data': {
description: 'Start data section',
syntax: '.data',
example: '.data # Data goes here'
},
'.global': {
description: 'Make symbol globally visible',
syntax: '.global symbol',
example: '.global main # main is visible to linker'
},
'.string': {
description: 'Define null-terminated string',
syntax: '.string "text"',
example: '.string "Hello World"'
},
'.word': {
description: 'Define 32-bit word',
syntax: '.word value',
example: '.word 42 # stores 32-bit value 42'
},
'.byte': {
description: 'Define 8-bit byte',
syntax: '.byte value',
example: '.byte 0xFF # stores 8-bit value 255'
}
};
export class RiscvHoverProvider implements vscode.HoverProvider {
provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult<vscode.Hover> {
const range = document.getWordRangeAtPosition(position);
if (!range) {
return undefined;
}
const word = document.getText(range).toLowerCase();
// Vérifier les instructions
if (INSTRUCTION_DOCS[word]) {
const doc = INSTRUCTION_DOCS[word];
const markdown = new vscode.MarkdownString();
markdown.appendCodeblock(`${doc.syntax}`, 'riscv-asm');
markdown.appendMarkdown(`**${doc.description}**\n\n`);
markdown.appendCodeblock(`${doc.example}`, 'riscv-asm');
return new vscode.Hover(markdown, range);
}
// Vérifier les registres
if (REGISTER_DOCS[word]) {
const doc = REGISTER_DOCS[word];
const markdown = new vscode.MarkdownString();
markdown.appendMarkdown(`**Register:** \`${word}\`\n\n`);
markdown.appendMarkdown(`${doc.description}`);
if (doc.abi_name) {
markdown.appendMarkdown(` (also known as \`${doc.abi_name}\`)`);
}
return new vscode.Hover(markdown, range);
}
// Vérifier les directives
if (DIRECTIVE_DOCS[word]) {
const doc = DIRECTIVE_DOCS[word];
const markdown = new vscode.MarkdownString();
markdown.appendCodeblock(`${doc.syntax}`, 'riscv-asm');
markdown.appendMarkdown(`**${doc.description}**\n\n`);
markdown.appendCodeblock(`${doc.example}`, 'riscv-asm');
return new vscode.Hover(markdown, range);
}
return undefined;
}
}

View File

@@ -0,0 +1,15 @@
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
});

View File

@@ -0,0 +1,136 @@
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "RISC-V Assembly",
"scopeName": "source.riscv-asm",
"patterns": [
{"include": "#comments"},
{"include": "#labels"},
{"include": "#directives"},
{"include": "#instructions"},
{"include": "#registers"},
{"include": "#numbers"},
{"include": "#strings"}
],
"repository": {
"comments": {
"patterns": [
{
"name": "comment.line.number-sign.riscv-asm",
"begin": "#",
"end": "$"
},
{
"name": "comment.block.riscv-asm",
"begin": "/\\*",
"end": "\\*/"
}
]
},
"labels": {
"patterns": [
{
"name": "entity.name.function.riscv-asm",
"match": "^\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*:"
}
]
},
"directives": {
"patterns": [
{
"name": "keyword.control.directive.riscv-asm",
"match": "\\b(\\.text|\\.data|\\.bss|\\.section|\\.global|\\.globl|\\.extern|\\.equ|\\.set|\\.align|\\.balign|\\.word|\\.dword|\\.byte|\\.half|\\.ascii|\\.asciiz|\\.string|\\.zero|\\.space|\\.skip|\\.org|\\.include)\\b"
}
]
},
"instructions": {
"patterns": [
{
"name": "keyword.mnemonic.arithmetic.riscv-asm",
"match": "\\b(add|addi|sub|lui|auipc|xor|xori|or|ori|and|andi|sll|slli|srl|srli|sra|srai|slt|slti|sltu|sltiu)\\b"
},
{
"name": "keyword.mnemonic.load-store.riscv-asm",
"match": "\\b(lb|lh|lw|ld|lbu|lhu|lwu|sb|sh|sw|sd)\\b"
},
{
"name": "keyword.mnemonic.branch.riscv-asm",
"match": "\\b(beq|bne|blt|bge|bltu|bgeu|jal|jalr)\\b"
},
{
"name": "keyword.mnemonic.system.riscv-asm",
"match": "\\b(ecall|ebreak|csrrw|csrrs|csrrc|csrrwi|csrrsi|csrrci|fence|fence\\.i)\\b"
},
{
"name": "keyword.mnemonic.multiplication.riscv-asm",
"match": "\\b(mul|mulh|mulhsu|mulhu|div|divu|rem|remu)\\b"
},
{
"name": "keyword.mnemonic.atomic.riscv-asm",
"match": "\\b(lr\\.w|sc\\.w|lr\\.d|sc\\.d|amoswap|amoadd|amoxor|amoand|amoor|amomin|amomax|amominu|amomaxu)\\b"
},
{
"name": "keyword.mnemonic.pseudoinstruction.riscv-asm",
"match": "\\b(nop|li|la|mv|not|neg|seqz|snez|sltz|sgtz|j|jr|ret|call|tail)\\b"
}
]
},
"registers": {
"patterns": [
{
"name": "variable.language.register.riscv-asm",
"match": "\\b(x[0-9]|x[12][0-9]|x3[01]|zero|ra|sp|gp|tp|t[0-6]|s[0-9]|s1[01]|a[0-7]|fp)\\b"
},
{
"name": "variable.language.register.floating.riscv-asm",
"match": "\\b(f[0-9]|f[12][0-9]|f3[01]|ft[0-9]|ft1[01]|fs[0-9]|fs1[01]|fa[0-7])\\b"
}
]
},
"numbers": {
"patterns": [
{
"name": "constant.numeric.hex.riscv-asm",
"match": "\\b0[xX][0-9a-fA-F]+\\b"
},
{
"name": "constant.numeric.binary.riscv-asm",
"match": "\\b0[bB][01]+\\b"
},
{
"name": "constant.numeric.octal.riscv-asm",
"match": "\\b0[0-7]+\\b"
},
{
"name": "constant.numeric.decimal.riscv-asm",
"match": "\\b[0-9]+\\b"
}
]
},
"strings": {
"patterns": [
{
"name": "string.quoted.double.riscv-asm",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "constant.character.escape.riscv-asm",
"match": "\\\\."
}
]
},
{
"name": "string.quoted.single.riscv-asm",
"begin": "'",
"end": "'",
"patterns": [
{
"name": "constant.character.escape.riscv-asm",
"match": "\\\\."
}
]
}
]
}
}
}

17
tsconfig.json Normal file
View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "Node16",
"target": "ES2022",
"outDir": "out",
"lib": [
"ES2022"
],
"sourceMap": true,
"rootDir": "src",
"strict": true, /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
}
}

View File

@@ -0,0 +1,44 @@
# Welcome to your VS Code Extension
## What's in the folder
* This folder contains all of the files necessary for your extension.
* `package.json` - this is the manifest file in which you declare your extension and command.
* The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesnt yet need to load the plugin.
* `src/extension.ts` - this is the main file where you will provide the implementation of your command.
* The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
* We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
## Get up and running straight away
* Press `F5` to open a new window with your extension loaded.
* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
* Set breakpoints in your code inside `src/extension.ts` to debug your extension.
* Find output from your extension in the debug console.
## Make changes
* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
## Explore the API
* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
## Run tests
* Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner)
* Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered.
* Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A`
* See the output of the test result in the Test Results view.
* Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder.
* The provided test runner will only consider files matching the name pattern `**.test.ts`.
* You can create folders inside the `test` folder to structure your tests any way you want.
## Go further
* [Follow UX guidelines](https://code.visualstudio.com/api/ux-guidelines/overview) to create extensions that seamlessly integrate with VS Code's native interface and patterns.
* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace.
* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
* Integrate to the [report issue](https://code.visualstudio.com/api/get-started/wrapping-up#issue-reporting) flow to get issue and feature requests reported by users.