Add simple error diagnostic from parser

This commit is contained in:
Sofia 2025-07-29 19:53:12 +03:00
parent bc59b6f575
commit 6619f1f0a9
16 changed files with 278 additions and 58 deletions

24
Cargo.lock generated
View File

@ -108,6 +108,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "dashmap"
version = "5.5.3"
@ -121,6 +127,20 @@ dependencies = [
"parking_lot_core",
]
[[package]]
name = "dashmap"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
@ -653,6 +673,8 @@ dependencies = [
name = "reid-lsp"
version = "0.1.0"
dependencies = [
"dashmap 6.1.0",
"reid",
"socket",
"tokio",
"tower-lsp",
@ -903,7 +925,7 @@ dependencies = [
"async-trait",
"auto_impl",
"bytes",
"dashmap",
"dashmap 5.5.3",
"futures",
"httparse",
"lsp-types",

View File

@ -16,7 +16,7 @@ BINARY="$(echo $1 | cut -d'.' -f1)"".out"
echo $1
cargo run --example cli $@ && \
cargo run --example cli $@ && \
./$BINARY ; echo "Return value: ""$?"
## Command from: clang -v hello.o -o test

View File

@ -52,7 +52,5 @@ fn main() {
else_b.terminate(TerminatorKind::Ret(add)).unwrap();
dbg!(&context);
context.compile(None, Vec::new());
}

View File

@ -223,7 +223,6 @@ impl Builder {
unsafe {
let mut modules = self.modules.borrow_mut();
let module = modules.get_unchecked_mut(module.0);
dbg!(module.functions.iter().map(|f| f.data.name.clone()).collect::<Vec<_>>());
module.functions.iter().find(|f| f.data.name == *name).map(|f| f.value)
}
}

View File

@ -123,8 +123,6 @@ impl CompiledModule {
let llvm_ir =
from_cstring(LLVMPrintModuleToString(self.module_ref)).expect("Unable to print LLVM IR to string");
println!("{}", llvm_ir);
let mut err = ErrorMessageHolder::null();
LLVMVerifyModule(
self.module_ref,

View File

@ -7,3 +7,5 @@ edition = "2024"
socket = "0.0.7"
tokio = { version = "1.47.0", features = ["full"] }
tower-lsp = "0.20.0"
reid = { path = "../reid", version = "1.0.0-beta.2", registry="gitea-teascade", features=[] }
dashmap = "6.1.0"

View File

@ -57,6 +57,9 @@ export function activate(context: ExtensionContext) {
client.info("hello");
workspace.onDidOpenTextDocument((e) => {
});
// Start the client. This will also launch the server
client.start();
}

View File

@ -1,19 +1,51 @@
use std::path::PathBuf;
use dashmap::DashMap;
use reid::ast::lexer::{FullToken, Position};
use reid::{compile_module, parse_module};
use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::*;
use tower_lsp::lsp_types::{
self, CompletionItem, CompletionOptions, CompletionParams, CompletionResponse, Diagnostic, DiagnosticSeverity,
DidChangeTextDocumentParams, DidOpenTextDocumentParams, Hover, HoverContents, HoverParams, HoverProviderCapability,
InitializeParams, InitializeResult, InitializedParams, MarkedString, MessageType, OneOf, Range, ServerCapabilities,
TextDocumentItem, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
};
use tower_lsp::{Client, LanguageServer, LspService, Server};
#[derive(Debug)]
struct Backend {
client: Client,
tokens: DashMap<String, Vec<FullToken>>,
ast: DashMap<String, reid::ast::Module>,
}
#[tower_lsp::async_trait]
impl LanguageServer for Backend {
async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
self.client
.log_message(MessageType::INFO, "Initializing Reid Language Server")
.await;
let sync = TextDocumentSyncOptions {
open_close: Some(true),
change: Some(TextDocumentSyncKind::FULL),
will_save: None,
will_save_wait_until: None,
save: None,
};
Ok(InitializeResult {
capabilities: ServerCapabilities {
hover_provider: Some(HoverProviderCapability::Simple(true)),
completion_provider: Some(CompletionOptions::default()),
completion_provider: Some(CompletionOptions { ..Default::default() }),
text_document_sync: Some(TextDocumentSyncCapability::Options(sync)),
workspace: Some(WorkspaceServerCapabilities {
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
supported: Some(true),
change_notifications: Some(OneOf::Left(true)),
}),
file_operations: None,
}),
..Default::default()
},
..Default::default()
@ -22,13 +54,146 @@ impl LanguageServer for Backend {
async fn initialized(&self, _: InitializedParams) {
self.client
.log_message(MessageType::INFO, "Reid Language Server initialized!")
.log_message(MessageType::INFO, "Reid Language Server initialized hello!")
.await;
}
async fn shutdown(&self) -> Result<()> {
Ok(())
}
async fn completion(&self, _: CompletionParams) -> Result<Option<CompletionResponse>> {
Ok(Some(CompletionResponse::Array(vec![
CompletionItem::new_simple("Hello".to_string(), "Some detail".to_string()),
CompletionItem::new_simple("Bye".to_string(), "More detail".to_string()),
])))
}
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
let path = PathBuf::from(params.text_document_position_params.text_document.uri.path());
let file_name = path.file_name().unwrap().to_str().unwrap().to_owned();
let tokens = self.tokens.get(&file_name).unwrap();
let position = params.text_document_position_params.position;
self.client
.log_message(
MessageType::INFO,
format!("line {}, col {}", position.line, position.character),
)
.await;
let token = tokens.iter().find(|tok| {
tok.position.1 == position.line + 1
&& (tok.position.0 <= position.character + 1
&& (tok.position.0 + tok.token.len() as u32) > position.character + 1)
});
Ok(Some(Hover {
contents: HoverContents::Scalar(MarkedString::String(format!("{:?}", token))),
range: None,
}))
}
async fn did_open(&self, params: DidOpenTextDocumentParams) {
self.client.log_message(MessageType::INFO, "opened!").await;
self.recompile(TextDocumentItem {
uri: params.text_document.uri,
language_id: params.text_document.language_id,
version: params.text_document.version,
text: params.text_document.text,
})
.await
}
async fn did_change(&self, params: DidChangeTextDocumentParams) {
self.client.log_message(MessageType::INFO, "changed!").await;
self.recompile(TextDocumentItem {
text: params.content_changes[0].text.clone(),
uri: params.text_document.uri,
version: params.text_document.version,
language_id: String::new(),
})
.await
}
}
impl Backend {
async fn recompile(&self, params: TextDocumentItem) {
let mut map = Default::default();
let path = PathBuf::from(params.uri.clone().path());
let file_name = path.file_name().unwrap().to_str().unwrap().to_owned();
let mut reid_error = None;
let mut tokens = None;
match parse_module(&params.text, file_name.clone(), &mut map) {
Ok(module) => {
self.client
.log_message(MessageType::INFO, format!("successfully parsed!"))
.await;
tokens = Some(module.1.clone());
match compile_module(module.0, module.1, &mut map, Some(path), true) {
Ok(_) => {}
Err(e) => {
reid_error = Some(e);
}
}
}
Err(_) => {}
}
if let Some(tokens) = &tokens {
if let Some(reid_error) = reid_error {
let mut diagnostics = Vec::new();
for error in reid_error.errors {
let meta = error.get_meta();
let positions = meta
.range
.into_position(&tokens)
.unwrap_or((Position(0, 0), Position(0, 0)));
self.client.log_message(MessageType::INFO, format!("{:?}", &meta)).await;
self.client
.log_message(MessageType::INFO, format!("{:?}", &tokens))
.await;
self.client
.log_message(MessageType::INFO, format!("{:?}", &positions))
.await;
diagnostics.push(Diagnostic {
range: Range {
start: lsp_types::Position {
line: ((positions.0.1 as i32) - 1).max(0) as u32,
character: ((positions.0.0 as i32) - 1).max(0) as u32,
},
end: lsp_types::Position {
line: ((positions.1.1 as i32) - 1).max(0) as u32,
character: ((positions.1.0 as i32) - 1).max(0) as u32,
},
},
severity: Some(DiagnosticSeverity::ERROR),
code: None,
code_description: None,
source: Some(error.get_type_str().to_owned()),
message: format!("{}", error),
related_information: None,
tags: None,
data: None,
});
self.client.log_message(MessageType::INFO, format!("{}", error)).await;
}
self.client
.publish_diagnostics(params.uri.clone(), diagnostics, Some(params.version))
.await;
} else {
self.client
.publish_diagnostics(params.uri.clone(), Vec::new(), Some(params.version))
.await;
}
}
if let Some(tokens) = tokens.take() {
self.tokens.insert(file_name.clone(), tokens);
}
}
}
#[tokio::main]
@ -36,6 +201,10 @@ async fn main() {
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let (service, socket) = LspService::new(|client| Backend { client });
let (service, socket) = LspService::new(|client| Backend {
client,
ast: DashMap::new(),
tokens: DashMap::new(),
});
Server::new(stdin, stdout, socket).serve(service).await;
}

View File

@ -10,6 +10,7 @@ edition = "2021"
default = ["color"]
color = ["colored"]
log_output = []
[dependencies]
## Make it easier to generate errors

View File

@ -12,7 +12,6 @@ fn main() -> Result<(), std::io::Error> {
libraries.push(libname);
}
dbg!(&filename);
let path = PathBuf::from(filename).canonicalize().unwrap();
let parent = path.with_extension("");
let llvm_ir_path = parent.with_extension("ll");
@ -21,6 +20,7 @@ fn main() -> Result<(), std::io::Error> {
let mir_path = parent.with_extension("mir");
let asm_path = parent.with_extension("asm");
#[cfg(feature = "log_output")]
let before = std::time::SystemTime::now();
let text = fs::read_to_string(&path)?;
@ -31,33 +31,39 @@ fn main() -> Result<(), std::io::Error> {
match compile_simple(&text, PathBuf::from(&path), Some(cpu), vec![features]) {
Ok((
CompileOutput {
triple,
triple: _triple,
assembly,
obj_buffer,
llvm_ir,
llvm_ir: _llvm_ir,
},
CustomIRs { llir, mir },
)) => {
println!("{}", llvm_ir);
#[cfg(feature = "log_output")]
{
println!("{}", _llvm_ir);
println!("Compiled with triple: {}\n", &_triple);
println!("Output LLVM IR to {:?}", llvm_ir_path);
println!("Output Assembly to {:?}", asm_path);
println!("Output Object-file to {:?}\n", object_path);
println!("Output LLIR-file to {:?}\n", llir_path);
println!("Output MIR-file to {:?}\n", mir_path);
}
let after = std::time::SystemTime::now();
println!("Compiled with triple: {}\n", &triple);
fs::write(&llvm_ir_path, &llvm_ir).expect("Could not write LLVM IR -file!");
println!("Output LLVM IR to {:?}", llvm_ir_path);
fs::write(&llvm_ir_path, &_llvm_ir).expect("Could not write LLVM IR -file!");
fs::write(&asm_path, &assembly).expect("Could not write Assembly-file!");
println!("Output Assembly to {:?}", asm_path);
fs::write(&object_path, &obj_buffer).expect("Could not write Object-file!");
println!("Output Object-file to {:?}\n", object_path);
fs::write(&llir_path, &llir).expect("Could not write LLIR-file!");
println!("Output LLIR-file to {:?}\n", llir_path);
fs::write(&mir_path, &mir).expect("Could not write MIR-file!");
println!("Output MIR-file to {:?}\n", mir_path);
println!(
"Compilation took: {:.2}ms\n",
(after.duration_since(before).unwrap().as_micros() as f32) / 1000.
);
#[cfg(feature = "log_output")]
{
let after = std::time::SystemTime::now();
println!(
"Compilation took: {:.2}ms\n",
(after.duration_since(before).unwrap().as_micros() as f32) / 1000.
);
println!("Linking {:?}", &object_path);
println!("Linking {:?}", &object_path);
}
let linker = std::env::var("LD").unwrap_or("ld".to_owned());
let mut linker = LDRunner::from_command(&linker).with_library("c");
@ -69,6 +75,7 @@ fn main() -> Result<(), std::io::Error> {
Err(e) => panic!("{}", e),
};
} else {
#[cfg(feature = "log_output")]
println!("Please input compiled file path!")
}
Ok(())

View File

@ -42,13 +42,11 @@ impl<'a, 'b> TokenStream<'a, 'b> {
/// Useful in conjunction with [`TokenStream::next`]
pub fn expecting_err<T: Into<String>>(&mut self, expected: T) -> Result<Error, Error> {
let next_token = self.peek().unwrap_or(Token::Eof);
let pos = self.next_token(self.position).0;
Ok(Error::Expected(
expected.into(),
next_token,
TokenRange {
start: self.position,
end: self.position,
},
TokenRange { start: pos, end: pos },
))
}
@ -173,7 +171,7 @@ impl<'a, 'b> TokenStream<'a, 'b> {
pub fn get_range_prev(&self) -> Option<TokenRange> {
self.ref_position.as_ref().map(|ref_pos| TokenRange {
start: **ref_pos,
end: self.position - 1,
end: self.previous_token(self.position).0,
})
}

View File

@ -728,7 +728,6 @@ impl mir::Statement {
mir::StmtKind::Let(NamedVariableRef(ty, name, meta), mutable, expression) => {
let value = expression.codegen(scope, &state)?.unwrap();
dbg!(&scope.allocator, &meta, &value.1);
let alloca = scope
.allocate(meta, &value.1)
.unwrap()

View File

@ -50,7 +50,7 @@ impl ErrorKind {
}
impl ErrorKind {
fn get_meta(&self) -> Metadata {
pub fn get_meta(&self) -> Metadata {
match &self {
ErrorKind::LexerError(error) => error.metadata,
ErrorKind::ParserError(error) => error.metadata,
@ -63,6 +63,18 @@ impl ErrorKind {
ErrorKind::MacroError(error) => error.metadata,
}
}
pub fn get_type_str(&self) -> &str {
match self {
ErrorKind::LexerError(_) => "lexer",
ErrorKind::ParserError(_) => "parser",
ErrorKind::TypeCheckError(_) => "typechecker",
ErrorKind::TypeInferenceError(_) => "type-inferrer",
ErrorKind::LinkerError(_) => "linker",
ErrorKind::MacroError(_) => "macro-pass",
ErrorKind::CodegenError(_) => "codegen",
}
}
}
impl PartialOrd for ErrorKind {
@ -120,7 +132,7 @@ impl ErrorModules {
#[derive(Debug, Clone, PartialEq)]
pub struct ReidError {
map: ErrorModules,
errors: Vec<ErrorKind>,
pub errors: Vec<ErrorKind>,
}
impl ReidError {
@ -185,9 +197,7 @@ impl std::fmt::Display for ReidError {
let module = self.map.module(&meta.source_module_id);
let position = if let Some(module) = module {
if let Some(tokens) = &module.tokens {
let range_tokens = meta.range.into_tokens(&tokens);
get_position(&range_tokens).or(meta.position.map(|p| (p, p)))
meta.range.into_position(tokens).or(meta.position.map(|p| (p, p)))
} else if let Some(position) = meta.position {
Some((position, position))
} else {
@ -237,6 +247,11 @@ impl TokenRange {
.take(self.end + 1 - self.start)
.collect::<Vec<_>>()
}
pub fn into_position<'v>(&self, tokens: &'v Vec<FullToken>) -> Option<(Position, Position)> {
let tokens = self.into_tokens(tokens);
get_position(&tokens)
}
}
fn get_position(tokens: &Vec<&FullToken>) -> Option<(Position, Position)> {

View File

@ -26,12 +26,11 @@ impl LDRunner {
let dyn_linker_path = find_objectfile(&self.dynamic_linker);
let crt1_path = find_objectfile("crt1.o");
#[cfg(feature = "log_output")]
println!("LDRunner: Using dynamic linker at: {:?}", dyn_linker_path);
let mut ld = Command::new(&self.command);
ld.arg("-dynamic-linker")
.arg(dyn_linker_path)
.arg(crt1_path);
ld.arg("-dynamic-linker").arg(dyn_linker_path).arg(crt1_path);
for library in &self.libraries {
ld.arg(format!("-l{}", library));
@ -41,22 +40,21 @@ impl LDRunner {
.arg("-o")
.arg(out_path.to_str().unwrap());
#[cfg(feature = "log_output")]
println!(
"LDRunner: Executing linker to objfile at {:?} => {:?}",
input_path, out_path
);
#[cfg(feature = "log_output")]
dbg!(&ld);
ld.spawn().expect("Unable to execute ld!");
thread::sleep(Duration::from_millis(100));
#[cfg(feature = "log_output")]
println!("Setting executable bit to {:?}..", out_path);
Command::new("chmod")
.arg("+x")
.arg(out_path)
.spawn()
.unwrap();
Command::new("chmod").arg("+x").arg(out_path).spawn().unwrap();
thread::sleep(Duration::from_millis(100));
}
}

View File

@ -72,7 +72,7 @@ use crate::{
mir::macros::{form_macros, MacroModule, MacroPass},
};
mod ast;
pub mod ast;
mod codegen;
pub mod error_raporting;
pub mod ld;
@ -93,6 +93,7 @@ pub fn parse_module<'map, T: Into<String>>(
map.set_tokens(id, tokens.clone());
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:#?}", &tokens);
Ok((id, tokens))
@ -127,6 +128,7 @@ pub fn compile_module<'map>(
};
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
dbg!(&ast_module);
Ok(ast_module.process(module_id))
@ -137,9 +139,11 @@ pub fn perform_all_passes<'map>(
module_map: &'map mut ErrorModules,
) -> Result<(), ReidError> {
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
dbg!(&context);
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:#}", &context);
let state = context.pass(&mut LinkerPass {
@ -154,10 +158,13 @@ pub fn perform_all_passes<'map>(
}
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:-^100}", "LINKER OUTPUT");
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:#}", &context);
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
dbg!(&state);
if !state.errors.is_empty() {
@ -179,10 +186,13 @@ pub fn perform_all_passes<'map>(
let state = context.pass(&mut macro_pass)?;
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:-^100}", "MACRO OUTPUT");
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:#}", &context);
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
dbg!(&state);
if !state.errors.is_empty() {
@ -217,12 +227,16 @@ pub fn perform_all_passes<'map>(
let state = context.pass(&mut TypeInference { refs: &mut refs })?;
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:-^100}", "TYPE INFERRER OUTPUT");
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{}", &refs);
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:#}", &context);
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
dbg!(&state);
if !state.errors.is_empty() {
@ -239,10 +253,13 @@ pub fn perform_all_passes<'map>(
let state = context.pass(&mut TypeCheck { refs: &refs })?;
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:-^100}", "TYPECHECKER OUTPUT");
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:#}", &context);
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
dbg!(&state);
if !state.errors.is_empty() {
@ -280,8 +297,10 @@ pub fn compile_and_pass<'map>(
perform_all_passes(&mut mir_context, module_map)?;
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:-^100}", "FINAL OUTPUT");
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{:#}", &mir_context);
let mut context = Context::new(format!("Reid ({})", env!("CARGO_PKG_VERSION")));
@ -291,6 +310,7 @@ pub fn compile_and_pass<'map>(
};
#[cfg(debug_assertions)]
#[cfg(feature = "log_output")]
println!("{}", &codegen_modules.context);
let compiled = codegen_modules.compile(cpu, features);

View File

@ -41,16 +41,7 @@ impl Metadata {
}
pub fn into_positions(&self, tokens: &Vec<FullToken>) -> Option<(Position, Position)> {
let mut iter = tokens
.iter()
.skip(self.range.start)
.take(self.range.end - self.range.start);
if let Some(first) = iter.next() {
let last = iter.last().unwrap_or(first);
Some((first.position, last.position.add(last.token.len() as u32)))
} else {
None
}
self.range.into_position(tokens)
}
}