Compare commits

..
Author SHA1 Message Date
selimaj-dev 2dc5cdc9d9 Release 0.1.7-alpha.0
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]

Generated by cargo-workspaces
2026-05-31 12:36:23 +02:00
selimaj-dev 893c981a41 Merge pull request #56 from mist-go/future-proofed-modules
Future proofed module stucture
2026-05-29 20:35:12 +02:00
selimaj-dev 0b3c4b8143 Module structure 2026-05-29 20:33:30 +02:00
selimaj-dev 86a1df7ad5 Mist Module system basic structure 2026-05-29 16:13:52 +02:00
selimaj-dev 3fa04cddd0 mist api lsp 2026-05-29 09:17:51 +02:00
selimaj-dev 371365b8cf Separated mist api 2026-05-29 09:04:58 +02:00
selimaj-dev b74da3255a Init mist-api 2026-05-29 08:58:42 +02:00
selimaj-dev 83e5e98641 Added mist.toml 2026-05-29 08:52:30 +02:00
selimaj-dev c774737074 Merge pull request #53 from mist-go/inheritance
Inheritance
2026-05-28 17:43:56 +02:00
selimaj-dev 9e1b9ad564 Fixed attributes 2026-05-28 16:28:20 +02:00
selimaj-dev 97b2cc4413 Working inheritance 2026-05-28 16:21:36 +02:00
selimaj-dev 99ff0ead2d Inheritance gen 2026-05-28 16:08:31 +02:00
selimaj-dev b0f3dccc87 Inheritance parsing 2026-05-28 15:55:00 +02:00
selimaj-dev 86a781c3f2 Merge pull request #52 from mist-go/parser-patches
Parser patches
2026-05-28 15:47:44 +02:00
selimaj-dev 583d51141f Fixed empty items 2026-05-28 15:44:52 +02:00
selimaj-dev ad11a3bd6a Improved match item span 2026-05-28 12:16:28 +02:00
selimaj-dev 7a2ba74d9c Working mut pattern 2026-05-28 11:52:40 +02:00
selimaj-dev 7007aa32f1 Refactored mutability 2026-05-28 11:45:26 +02:00
selimaj-dev 3444f93546 Updated codegen 2026-05-28 11:36:28 +02:00
selimaj-dev e09c7926e5 Updated pattern ast 2026-05-28 11:34:14 +02:00
selimaj-dev 83e98573aa Updated pattern grammar 2026-05-28 11:30:47 +02:00
selimaj-dev 3368696523 Merge pull request #51 from mist-go/bootstrapped-transpiler
Bootstrapped transpiler in mist
2026-05-25 19:45:57 +02:00
selimaj-dev ec1864334c Bootstrapped transpiler in mist 2026-05-25 16:06:04 +02:00
selimaj-dev 7c4595aeb9 Updated cargo.toml for lsp 2026-05-24 18:36:13 +02:00
selimaj-dev 8202cd6773 Merge pull request #48 from mist-go/lsp-patches-and-completions
Lsp patches and completions
2026-05-24 17:44:37 +02:00
selimaj-dev ecdbc9416a Build all on initialize 2026-05-24 17:38:35 +02:00
selimaj-dev 24134ec925 Production safe analyzer 2026-05-24 17:24:37 +02:00
selimaj-dev dbf3a80387 Working completions 2026-05-24 17:04:08 +02:00
selimaj-dev cdffc87161 Using ropey 2026-05-24 16:49:53 +02:00
selimaj-dev fbf60944a8 using ropey 2026-05-24 16:24:22 +02:00
selimaj-dev 5a6adbc5e4 updated capabilities 2026-05-24 16:19:51 +02:00
selimaj-dev 354965b425 Completion 2026-05-24 15:45:01 +02:00
selimaj-dev f5fcbbacb6 did change and close 2026-05-24 15:11:24 +02:00
selimaj-dev e358636e41 Updated version 2026-05-24 14:51:01 +02:00
selimaj-dev 2c5a845c57 Merge pull request #47 from mist-go/closures-and-bodies
Closures and fix statement bodies
2026-05-24 14:47:32 +02:00
selimaj-dev f21cd5a927 Fixed statement bodies 2026-05-24 14:43:09 +02:00
selimaj-dev 63cc20107c Soft returning refactor 2026-05-24 13:20:08 +02:00
selimaj-dev f4fe38140a statement body parsing 2026-05-24 12:55:33 +02:00
selimaj-dev 6551aee03b Fixed block propaganda 2026-05-24 12:43:35 +02:00
selimaj-dev 1c2cc94cb9 Closures 2026-05-24 12:29:42 +02:00
selimaj-dev fde41b9a22 Merge pull request #46 from mist-go/working-lsp
Working lsp
2026-05-24 12:00:29 +02:00
selimaj-dev 9eec07ca2e Improved compiler error message 2026-05-24 11:57:17 +02:00
selimaj-dev 5bfccb0fc4 Improved error handling 2026-05-24 10:41:28 +02:00
selimaj-dev bad6034ba5 Working goto with mist 2026-05-24 10:11:06 +02:00
selimaj-dev f05849ee45 Working did open and goto 2026-05-24 09:34:02 +02:00
selimaj-dev c36d07f0dd Working goto request 2026-05-24 07:56:31 +02:00
selimaj-dev f181806f65 Working request 2026-05-24 07:34:04 +02:00
selimaj-dev 8b0f2ac1b9 Working req res 2026-05-24 07:08:23 +02:00
selimaj-dev 64c612fe3c Removed unnecessary package 2026-05-24 07:03:46 +02:00
selimaj-dev 831fa544fc Pending 2026-05-24 07:01:53 +02:00
selimaj-dev 9b1acb8cc3 Working req res 2026-05-24 06:29:58 +02:00
selimaj-dev b2f5f2fdc8 Sending goto to rust 2026-05-23 15:40:47 +02:00
selimaj-dev c405a2059e Improved rust analyzer client 2026-05-23 15:26:15 +02:00
selimaj-dev ff58a92b3f Rust analyzer init 2026-05-23 14:55:44 +02:00
selimaj-dev 763473ef57 Merge pull request #45 from mist-go/working-lsp-diagnostics
Working lsp diagnostics
2026-05-23 11:27:45 +02:00
selimaj-dev eb24767617 idk 2026-05-23 11:27:05 +02:00
selimaj-dev fa00b3a199 Position injection 2026-05-23 10:14:23 +02:00
selimaj-dev b149bcde4a Refactored transpiler LSP 2026-05-23 09:50:04 +02:00
selimaj-dev 77b6991226 Improved error system LSp 2026-05-23 08:52:45 +02:00
selimaj-dev b96a6f154d errors 2026-05-23 04:36:56 +02:00
selimaj-dev 9f6ec4a20d Working LSP error checking 2026-05-22 15:53:25 +02:00
selimaj-dev 52efdda744 On save 2026-05-22 09:59:13 +02:00
selimaj-dev 97e8f32473 Working lsp build 2026-05-22 09:29:35 +02:00
selimaj-dev 21019cd54f Inject cargo error fix 2026-05-22 04:39:37 +02:00
selimaj-dev bb7e78d042 Fixed error line 2026-05-22 04:35:53 +02:00
38 changed files with 1871 additions and 360 deletions
Generated
+42 -15
View File
@@ -130,9 +130,9 @@ dependencies = [
[[package]] [[package]]
name = "dashmap" name = "dashmap"
version = "6.2.1" version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"crossbeam-utils", "crossbeam-utils",
@@ -442,37 +442,48 @@ dependencies = [
[[package]] [[package]]
name = "mist-analyzer" name = "mist-analyzer"
version = "0.0.2" version = "0.1.7-alpha.0"
dependencies = [ dependencies = [
"dashmap 6.2.1", "cargo_metadata",
"dashmap 6.1.0",
"mist-codegen", "mist-codegen",
"mist-parser", "mist-parser",
"ropey",
"serde",
"serde_json",
"tokio", "tokio",
"tower-lsp", "tower-lsp",
] ]
[[package]] [[package]]
name = "mist-codegen" name = "mist-api"
version = "0.1.1" version = "0.1.7-alpha.0"
dependencies = [
"mist-parser",
]
[[package]]
name = "mist-lang"
version = "0.0.4-alpha0"
dependencies = [ dependencies = [
"cargo_metadata", "cargo_metadata",
"mist-analyzer",
"mist-codegen", "mist-codegen",
"mist-parser", "mist-parser",
"serde", "serde",
"serde_json", "serde_json",
] ]
[[package]]
name = "mist-codegen"
version = "0.1.7-alpha.0"
dependencies = [
"mist-parser",
]
[[package]]
name = "mist-lang"
version = "0.1.7-alpha.0"
dependencies = [
"mist-analyzer",
"mist-api",
]
[[package]] [[package]]
name = "mist-parser" name = "mist-parser"
version = "0.1.5" version = "0.1.7-alpha.0"
dependencies = [ dependencies = [
"pest", "pest",
"pest_derive", "pest_derive",
@@ -620,6 +631,16 @@ dependencies = [
"bitflags 2.11.1", "bitflags 2.11.1",
] ]
[[package]]
name = "ropey"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
dependencies = [
"smallvec",
"str_indices",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@@ -739,6 +760,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "str_indices"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.117" version = "2.0.117"
+9 -12
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "mist-lang" name = "mist-lang"
version = "0.0.4-alpha0" version = "0.1.7-alpha.0"
edition = "2024" edition = "2024"
description = "The Mist programming language compiler" description = "The Mist programming language compiler"
license = "Apache-2.0" license = "Apache-2.0"
@@ -16,19 +16,15 @@ include = [
] ]
[dependencies] [dependencies]
mist-parser = { workspace = true }
mist-analyzer = { workspace = true } mist-analyzer = { workspace = true }
mist-codegen = { workspace = true } mist-api = { workspace = true }
serde = { version = "1.0.228", features = ["serde_derive"] }
serde_json = "1.0.149"
cargo_metadata = "0.23.1"
[workspace] [workspace]
members = [ members = [
"crates/parser", "crates/parser",
"crates/analyzer", "crates/analyzer",
"crates/codegen", "crates/codegen",
"crates/mist-api",
] ]
exclude = [ exclude = [
@@ -36,17 +32,18 @@ exclude = [
] ]
[workspace.dependencies] [workspace.dependencies]
mist-parser = { path = "crates/parser", version = "0.1.5" } mist-parser = { path = "crates/parser", version = "0.1.7-alpha.0" }
mist-analyzer = { path = "crates/analyzer", version = "0.0.2" } mist-analyzer = { path = "crates/analyzer", version = "0.1.7-alpha.0" }
mist-codegen = { path = "crates/codegen", version = "0.1.1" } mist-codegen = { path = "crates/codegen", version = "0.1.7-alpha.0" }
mist-api = { path = "crates/mist-api", version = "0.1.7-alpha.0" }
[[bin]] [[bin]]
name = "mist" name = "mist"
path = ".mist/src/main.rs" path = ".mist/src/main.rs"
[[bin]] [[bin]]
name = "mist-header" name = "mist-lsp"
path = "src/_header.rs" path = ".mist/lsp/main.rs"
[[bin]] [[bin]]
name = "mist-analyzer" name = "mist-analyzer"
+1
View File
@@ -0,0 +1 @@
package = "main.mist"
+9
View File
@@ -0,0 +1,9 @@
@echo off
cd D:\projects\mist\mist
mist build >nul 2>&1
cd D:\projects\mist\mist-lsp-test
D:\projects\mist\mist\target\debug\mist-analyzer.exe
+7 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "mist-analyzer" name = "mist-analyzer"
version = "0.0.2" version = "0.1.7-alpha.0"
edition = "2024" edition = "2024"
description = "The Mist programming language Analyzer LSP" description = "The Mist programming language Analyzer LSP"
license = "Apache-2.0" license = "Apache-2.0"
@@ -12,6 +12,11 @@ categories = ["compilers"]
mist-parser = { workspace = true } mist-parser = { workspace = true }
mist-codegen = { workspace = true } mist-codegen = { workspace = true }
dashmap = "6.2.1" dashmap = "6.1.0"
tokio = { version = "1.52.3", features = ["full", "macros", "rt", "rt-multi-thread"] } tokio = { version = "1.52.3", features = ["full", "macros", "rt", "rt-multi-thread"] }
tower-lsp = "0.20.0" tower-lsp = "0.20.0"
cargo_metadata = "0.23.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
ropey = "1"
+139
View File
@@ -0,0 +1,139 @@
use std::{
collections::HashMap,
env, fs,
path::{MAIN_SEPARATOR, PathBuf},
process::{Command, Stdio},
};
use cargo_metadata::{CompilerMessage, Message};
use mist_parser::rev_mapper::{RustMap, find_mapping, get_mapping};
#[derive(Debug, Clone)]
pub struct MistDiagnosticMessage {
pub message: String,
pub file_path: PathBuf,
pub file_name: String,
pub line: usize,
pub column: usize,
}
#[derive(Debug, Clone)]
pub enum MistDiagnostic {
Error(MistDiagnosticMessage),
Warning(MistDiagnosticMessage),
Rust(CompilerMessage),
}
pub fn build(mut args: Vec<String>, root: PathBuf) -> Vec<MistDiagnostic> {
args.insert(1, "--message-format=json".to_string());
let is_root = root == env::current_dir().expect("Failed getting env");
let mut command = Command::new("cargo")
.args(args)
.stdout(Stdio::piped())
.spawn()
.expect("Failed to run cargo");
let mut reader = std::io::BufReader::new(command.stdout.take().expect("Failed to get reader"));
let mut diagnostics = Vec::new();
let mut mapping = HashMap::new();
let mist_src = format!(".mist{MAIN_SEPARATOR}lsp");
for message in cargo_metadata::Message::parse_stream(&mut reader) {
match message {
Ok(Message::CompilerMessage(msg)) => {
for span in &msg.message.spans {
if span.is_primary {
let rust_path = root.join(&span.file_name);
let mist_file = span
.file_name
.replacen(&mist_src, "src", 1)
.trim_end_matches(".rs")
.to_string()
+ ".mist";
let mist_path = root.join(&mist_file);
if !fs::exists(&mist_path).expect("Unable to check if mist file exists") {
diagnostics.push(MistDiagnostic::Rust(msg));
break;
}
let map = mapping.entry(rust_path.clone()).or_insert_with(|| {
get_mapping(
&fs::read_to_string(rust_path)
.expect("Failed to read file for mapping"),
)
});
let mist_span =
find_mapping(&map, &RustMap(span.line_end, span.column_start))
.expect("Unable to find mapping");
let mist_msg = MistDiagnosticMessage {
message: format!(
"{}: {}",
msg.message.message,
span.label.clone().unwrap_or_default()
),
file_name: if is_root {
mist_file
} else {
mist_path.to_string_lossy().to_string()
},
file_path: mist_path,
line: mist_span.1.0,
column: mist_span.1.1,
};
match msg.message.level {
cargo_metadata::diagnostic::DiagnosticLevel::Error => {
diagnostics.push(MistDiagnostic::Error(mist_msg))
}
cargo_metadata::diagnostic::DiagnosticLevel::Warning => {
diagnostics.push(MistDiagnostic::Warning(mist_msg))
}
_ => {}
}
}
}
}
Ok(Message::BuildFinished(_)) => {
command.wait().unwrap();
return diagnostics;
}
Ok(Message::TextLine(text)) => println!("{text}"),
_ => {}
}
}
diagnostics
}
pub fn get_line(
files: &mut HashMap<PathBuf, Vec<String>>,
msg: &MistDiagnosticMessage,
) -> Option<String> {
let src_path = msg.file_path.clone();
let lines = files.entry(src_path.clone()).or_insert_with(|| {
fs::read_to_string(src_path)
.expect("Unable to read mist file")
.lines()
.into_iter()
.map(String::from)
.collect()
});
lines.get(msg.line - 1).map(|v| v.trim().to_string())
}
+632 -16
View File
@@ -1,49 +1,552 @@
pub mod builder;
pub mod rust_analyzer;
pub mod transpiler;
use std::collections::{HashMap, HashSet};
use std::path::{Component, PathBuf};
use std::sync::Arc;
use mist_parser::rev_mapper;
use ropey::Rope;
use tokio::sync::Mutex;
use tower_lsp::jsonrpc::Result; use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::notification::Notification;
use tower_lsp::lsp_types::{self, *};
use tower_lsp::{Client, LanguageServer, LspService, Server}; use tower_lsp::{Client, LanguageServer, LspService, Server};
use crate::builder::MistDiagnostic;
use crate::rust_analyzer::RustAnalyzer;
#[derive(Debug)] #[derive(Debug)]
struct Backend { struct Backend {
client: Client, client: Client,
workspace_folder: Arc<Mutex<Option<PathBuf>>>,
previous_diagnostics: Arc<Mutex<HashMap<Url, Vec<Diagnostic>>>>,
rust_analyzer: Arc<Mutex<RustAnalyzer>>,
mapping: Arc<Mutex<HashMap<PathBuf, HashSet<(rev_mapper::RustMap, rev_mapper::MistMap)>>>>,
documents: Arc<Mutex<HashMap<PathBuf, Rope>>>,
}
/// Helper function to force percent-encoding on Windows drive colons
/// so that the URLs exactly match what VS Code/LSP clients send.
fn clean_lsp_url(path: &std::path::Path) -> Option<Url> {
let mut url_str = Url::from_file_path(path).ok()?.to_string();
// Look for Windows patterns like file:///D: or file:///d: and switch to %3A
if url_str.starts_with("file:///I:") || url_str.starts_with("file:///i:") || // Catch-all or explicit check:
(url_str.len() > 10 && url_str.as_bytes()[11] == b':')
{
// Safely replace the first colon occurring after "file:///"
if let Some(pos) = url_str.find(':') {
if pos == 11 {
// Double check it's the drive letter colon
url_str.replace_range(pos..=pos, "%3A");
}
}
}
Url::parse(&url_str).ok()
}
impl Backend {
async fn get_mist_location(&self, rs_loc: Location) -> Location {
let rs_path = match rs_loc.uri.to_file_path() {
Ok(path) => path,
Err(_) => return rs_loc,
};
match self.mapping.lock().await.get(&rs_path) {
Some(mapping) => {
// FIX: Match the find_mapping operation instead of calling .expect("Failed to map")
match rev_mapper::find_mapping(
mapping,
&rev_mapper::RustMap(
rs_loc.range.start.line as usize,
rs_loc.range.start.character as usize,
),
) {
Some((_, rev_mapper::MistMap(line, character))) => Location {
uri: Url::from_file_path(from_rust_to_mist(rs_path)).unwrap(),
range: Range {
start: Position {
line: line as u32 - 1,
character: character as u32,
},
end: Position {
line: line as u32 - 1,
character: character as u32 + 1,
},
},
},
None => rs_loc,
}
}
None => rs_loc,
}
}
} }
#[tower_lsp::async_trait] #[tower_lsp::async_trait]
impl LanguageServer for Backend { impl LanguageServer for Backend {
async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> { async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
let mut res = InitializeResult::default(); let mut res = InitializeResult::default();
res.capabilities.text_document_sync = Some(TextDocumentSyncCapability::Options(
TextDocumentSyncOptions {
open_close: Some(true),
change: Some(TextDocumentSyncKind::FULL),
will_save: Some(false),
will_save_wait_until: Some(false),
save: Some(SaveOptions::default().into()),
},
));
res.capabilities.completion_provider = Some(CompletionOptions { res.capabilities.completion_provider = Some(CompletionOptions {
resolve_provider: None, resolve_provider: Some(true),
trigger_characters: None, trigger_characters: Some(vec![
":".to_owned(),
".".to_owned(),
"'".to_owned(),
"(".to_owned(),
]),
all_commit_characters: None, all_commit_characters: None,
completion_item: Some(CompletionOptionsCompletionItem {
label_details_support: None,
}),
work_done_progress_options: WorkDoneProgressOptions { work_done_progress_options: WorkDoneProgressOptions {
work_done_progress: None, work_done_progress: None,
}, },
completion_item: None, });
res.capabilities.definition_provider = Some(OneOf::Left(true));
let folder_path = params
.workspace_folders
.as_ref()
.and_then(|folders| folders.first())
.and_then(|folder| folder.uri.to_file_path().ok());
if let Some(ref path) = folder_path {
*self.workspace_folder.lock().await = Some(path.clone());
}
let workspace_folder = self.workspace_folder.clone();
let analyzer = self.rust_analyzer.clone();
let documents = self.documents.clone();
let mapping = self.mapping.clone();
tokio::spawn(async move {
if let Some(root) = &*workspace_folder.lock().await {
let src_root = root.join("src");
transpiler::build(root);
analyzer
.lock()
.await
.initialize(root)
.await
.expect("Failed to initialize rust analyzer");
// ---- LOAD ALL .MIST FILES ----
let mut files = Vec::new();
collect_mist_files(&src_root, &mut files);
for file in files {
if let Ok(text) = std::fs::read_to_string(&file) {
if let Ok(transpiled) = transpiler::transpile_text(&text) {
let rust_path = from_mist_to_rust(file.clone());
// store document
documents
.lock()
.await
.insert(file.clone(), Rope::from_str(&text));
// store mapping
mapping
.lock()
.await
.insert(rust_path, rev_mapper::get_mapping(&transpiled));
}
}
}
eprintln!("Ready to use");
}
}); });
Ok(res) Ok(res)
} }
async fn initialized(&self, _: InitializedParams) { async fn initialized(&self, _: InitializedParams) {
self.rust_analyzer
.lock()
.await
.initialized()
.await
.expect("Failed to initialize rust analyzer");
self.client self.client
.log_message(MessageType::INFO, "server initialized!") .log_message(MessageType::INFO, "server initialized!")
.await; .await;
} }
async fn completion(&self, _params: CompletionParams) -> Result<Option<CompletionResponse>> {
self.client
.log_message(MessageType::INFO, "getting completion")
.await;
Ok(Some(CompletionResponse::Array(vec![
CompletionItem::new_simple("new".to_string(), "The new keyword".to_string()),
])))
}
async fn shutdown(&self) -> Result<()> { async fn shutdown(&self) -> Result<()> {
Ok(()) Ok(())
} }
async fn did_save(&self, _: DidSaveTextDocumentParams) {
self.client
.log_message(MessageType::INFO, "Processing did_save event")
.await;
if let Some(root) = &*self.workspace_folder.lock().await {
transpiler::build(root);
}
let workspace_root = match self.workspace_folder.lock().await.clone() {
Some(root) => root,
None => {
self.client
.log_message(
MessageType::WARNING,
"Skipping diagnostics: No active workspace folder found",
)
.await;
return;
}
};
let diagnostics_raw = builder::build(
vec![
"check".to_string(),
"--workspace".to_string(),
"--all-targets".to_string(),
],
workspace_root,
);
let mut diagnostics = HashMap::new();
for diag in diagnostics_raw.iter() {
let (msg, severity) = match diag {
MistDiagnostic::Error(msg) => (msg, DiagnosticSeverity::ERROR),
MistDiagnostic::Warning(msg) => (msg, DiagnosticSeverity::WARNING),
MistDiagnostic::Rust(_) => {
continue;
}
};
let line = (msg.line as u32).saturating_sub(1);
let column = (msg.column as u32).saturating_sub(1);
let url = match clean_lsp_url(&msg.file_path) {
Some(u) => u,
None => continue,
};
let diagnostic_item = Diagnostic {
range: Range {
start: Position {
line,
character: column,
},
end: Position {
line,
character: u32::MAX,
},
},
severity: Some(severity),
code: None,
source: Some("mist-analyzer".to_string()),
message: msg.message.clone(),
related_information: None,
tags: None,
data: None,
code_description: None,
};
diagnostics
.entry(url)
.or_insert_with(Vec::new)
.push(diagnostic_item);
}
for (file, _) in self.previous_diagnostics.lock().await.iter() {
self.client
.publish_diagnostics(file.clone(), Vec::new(), None)
.await;
}
for (file, diag) in &diagnostics {
self.client
.publish_diagnostics(file.clone(), diag.clone(), None)
.await;
}
*self.previous_diagnostics.lock().await = diagnostics;
}
async fn did_open(&self, mut params: DidOpenTextDocumentParams) {
if params.text_document.language_id == "mist" {
let original_text = params.text_document.text.clone();
self.documents.lock().await.insert(
params.text_document.uri.to_file_path().unwrap(),
Rope::from_str(&original_text),
);
params.text_document.language_id = "rust".to_string();
match transpiler::transpile_text(&original_text) {
Ok(transpiled_text) => {
params.text_document.text = transpiled_text;
let rust_path =
from_mist_to_rust(params.text_document.uri.to_file_path().unwrap());
params.text_document.uri = Url::from_file_path(&rust_path).unwrap();
self.mapping.lock().await.insert(
rust_path,
rev_mapper::get_mapping(&params.text_document.text),
);
}
Err(e) => {
self.client
.log_message(
MessageType::WARNING,
format!("MIST-LSP: Syntax invalid during open/change: {:?}", e),
)
.await;
return;
}
}
}
if let Ok(mut ra) = self.rust_analyzer.try_lock() {
let _ = ra
.notify(notification::DidOpenTextDocument::METHOD, params)
.await;
}
}
async fn did_change(&self, mut params: DidChangeTextDocumentParams) {
let mist_path = params.text_document.uri.to_file_path().unwrap();
let rust_path = from_mist_to_rust(mist_path.clone());
let rust_uri = Url::from_file_path(&rust_path).unwrap();
if let Some(change) = params.content_changes.first_mut() {
self.documents
.lock()
.await
.insert(mist_path, Rope::from_str(&change.text));
match transpiler::transpile_text(&change.text) {
Ok(transpiled_text) => {
change.text = transpiled_text;
self.mapping
.lock()
.await
.insert(rust_path, rev_mapper::get_mapping(&change.text));
}
Err(e) => {
self.client
.log_message(
MessageType::WARNING,
format!("MIST-LSP: Syntax invalid during change: {:?}", e),
)
.await;
return;
}
}
}
params.text_document.uri = rust_uri;
if let Ok(mut ra) = self.rust_analyzer.try_lock() {
let _ = ra
.notify(notification::DidChangeTextDocument::METHOD, params)
.await;
}
}
async fn did_close(&self, mut params: DidCloseTextDocumentParams) {
self.client
.log_message(MessageType::INFO, "MIST-LSP: Processing did_close event")
.await;
let rust_path = from_mist_to_rust(params.text_document.uri.to_file_path().unwrap());
let mist_path = params.text_document.uri.to_file_path().unwrap();
self.documents.lock().await.remove(&mist_path);
self.mapping.lock().await.remove(&rust_path);
params.text_document.uri = Url::from_file_path(&rust_path).unwrap();
if let Ok(mut ra) = self.rust_analyzer.try_lock() {
let _ = ra
.notify(notification::DidCloseTextDocument::METHOD, params)
.await;
}
}
async fn goto_definition(
&self,
params: GotoDefinitionParams,
) -> Result<Option<GotoDefinitionResponse>> {
let file_path = params
.text_document_position_params
.text_document
.uri
.to_file_path()
.unwrap();
let source = match self.documents.lock().await.get(&file_path) {
Some(doc) => doc.clone(),
None => return Ok(None),
};
let inject = "__mist_23";
let injected_source = insert_at_position(
&source,
params.text_document_position_params.position.line as usize + 1,
params.text_document_position_params.position.character as usize,
&inject,
);
let output = Rope::from_str(
&match transpiler::transpile_text(&injected_source.to_string()) {
Ok(out) => out,
Err(_) => return Ok(None),
},
);
let (line, character) = match find_row_col(&output, inject) {
Some(coords) => coords,
None => return Ok(None),
};
let uri =
Url::from_file_path(from_mist_to_rust(file_path)).expect("failed to generate rs url");
let rs_res = self
.rust_analyzer
.lock()
.await
.request::<request::GotoDefinition>(lsp_types::GotoDefinitionParams {
text_document_position_params: lsp_types::TextDocumentPositionParams {
position: lsp_types::Position {
line: line as u32,
character: character as u32,
},
text_document: lsp_types::TextDocumentIdentifier { uri },
},
partial_result_params: lsp_types::PartialResultParams::default(),
work_done_progress_params: lsp_types::WorkDoneProgressParams::default(),
})
.await
.expect("Failed to send to rust");
Ok(match rs_res {
Some(GotoDefinitionResponse::Array(arr)) => {
let mut mapped_arr = Vec::new();
for rs_loc in arr {
let mapped_loc = self.get_mist_location(rs_loc).await;
mapped_arr.push(mapped_loc);
}
Some(GotoDefinitionResponse::Array(mapped_arr))
}
_ => rs_res,
})
}
async fn completion(&self, mut params: CompletionParams) -> Result<Option<CompletionResponse>> {
self.client
.log_message(MessageType::INFO, "COMPLETEING!")
.await;
let file_path = params
.text_document_position
.text_document
.uri
.to_file_path()
.unwrap();
let source = match self.documents.lock().await.get(&file_path) {
Some(doc) => doc.clone(),
None => return Ok(None),
};
let inject = "__mist_23";
let injected_source = insert_at_position(
&source,
params.text_document_position.position.line as usize + 1,
params.text_document_position.position.character as usize,
&inject,
);
let output = Rope::from_str(
&match transpiler::transpile_text(&injected_source.to_string()) {
Ok(out) => out,
Err(_) => return Ok(None),
},
);
let (line, character) = match find_row_col(&output, inject) {
Some(coords) => coords,
None => return Ok(None),
};
let uri =
Url::from_file_path(from_mist_to_rust(file_path)).expect("failed to generate rs url");
params.text_document_position.text_document.uri = uri;
params.text_document_position.position.line = line as u32;
params.text_document_position.position.character = character as u32;
let rs_res = self
.rust_analyzer
.lock()
.await
.request::<request::Completion>(params)
.await
.expect("Failed to send to rust");
Ok(rs_res.map(|rs_res| match rs_res {
CompletionResponse::Array(items) => {
CompletionResponse::Array(items.into_iter().map(simplify_item).collect())
}
CompletionResponse::List(list) => {
CompletionResponse::Array(list.items.into_iter().map(simplify_item).collect())
}
}))
}
async fn completion_resolve(&self, params: CompletionItem) -> Result<CompletionItem> {
match self
.rust_analyzer
.lock()
.await
.request::<request::ResolveCompletionItem>(params.clone())
.await
{
Ok(o) => Ok(o),
_ => Ok(params),
}
}
} }
#[tokio::main] #[tokio::main]
@@ -51,6 +554,119 @@ pub async fn start() {
let stdin = tokio::io::stdin(); let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout(); let stdout = tokio::io::stdout();
let (service, socket) = LspService::new(|client| Backend { client }); let (service, socket) = LspService::new(|client| Backend {
client,
workspace_folder: Arc::new(Mutex::new(None)),
previous_diagnostics: Arc::new(Mutex::new(HashMap::new())),
mapping: Arc::new(Mutex::new(HashMap::new())),
documents: Arc::new(Mutex::new(HashMap::new())),
rust_analyzer: Arc::new(Mutex::new(
RustAnalyzer::new().expect("Failed to create rust analyzer"),
)),
});
Server::new(stdin, stdout, socket).serve(service).await; Server::new(stdin, stdout, socket).serve(service).await;
} }
pub fn from_mist_to_rust(mut path: PathBuf) -> PathBuf {
path.set_extension("rs");
let mut comps: Vec<Component> = path.components().collect();
if let Some(pos) = comps.iter().rposition(|c| c.as_os_str() == "src") {
let replacement = std::path::Path::new(".mist/lsp");
comps.splice(pos..=pos, replacement.components());
comps.iter().collect()
} else {
path
}
}
pub fn from_rust_to_mist(mut path: PathBuf) -> PathBuf {
// reverse extension
path.set_extension("mist");
let comps: Vec<Component> = path.components().collect();
// pattern we originally inserted: ".mist/lsp"
let pattern: Vec<Component> = std::path::Path::new(".mist/lsp").components().collect();
// find the last occurrence of the pattern
if let Some(pos) = comps
.windows(pattern.len())
.rposition(|window| window == pattern.as_slice())
{
let mut new_comps = comps.clone();
// replace the matched range with "src"
new_comps.splice(
pos..pos + pattern.len(),
std::iter::once(Component::Normal(std::ffi::OsStr::new("src"))),
);
new_comps.iter().collect()
} else {
path
}
}
fn insert_at_position(rope: &Rope, line: usize, col: usize, insert: &str) -> Rope {
let mut rope = rope.clone();
let line_idx = line.saturating_sub(1);
let col_idx = col.saturating_sub(1);
let line_idx = line_idx.min(rope.len_lines().saturating_sub(1));
let line_start = rope.line_to_char(line_idx);
let line_slice = rope.line(line_idx);
let line_len = line_slice.len_chars();
let col_idx = col_idx.min(line_len);
let idx = line_start + col_idx;
rope.insert(idx, insert);
rope
}
fn find_row_col(rope: &Rope, needle: &str) -> Option<(usize, usize)> {
let text = rope.to_string();
let byte_idx = text.find(needle)?;
let char_idx = text[..byte_idx].chars().count();
let line_idx = rope.char_to_line(char_idx);
let line_start = rope.line_to_char(line_idx);
let col_idx = char_idx - line_start;
Some((line_idx, col_idx + 1))
}
fn simplify_item(mut item: CompletionItem) -> CompletionItem {
item.text_edit = None;
item.additional_text_edits = None;
item.command = None;
item
}
fn collect_mist_files(dir: &PathBuf, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_mist_files(&path, out);
} else if path.extension().and_then(|e| e.to_str()) == Some("mist") {
out.push(path);
}
}
}
+297
View File
@@ -0,0 +1,297 @@
use std::{collections::HashMap, path::PathBuf, process::Stdio, sync::Arc, time::Duration};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::{Value, json};
use tokio::{
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
sync::{Mutex, oneshot},
time::timeout,
};
use tower_lsp::lsp_types::{
self, ClientCapabilities, InitializeParams, InitializedParams, Url, WorkspaceFolder,
notification::{Initialized, Notification},
request::{self, Request},
};
const MAX_CONTENT_LENGTH: usize = 50 * 1024 * 1024;
const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug, Deserialize)]
pub struct JsonRpcResponse<T> {
pub jsonrpc: String,
pub id: Option<serde_json::Value>,
pub result: Option<T>,
pub error: Option<JsonRpcError>,
}
#[derive(Debug, Deserialize)]
pub struct JsonRpcError {
pub code: i64,
pub message: String,
pub data: Option<serde_json::Value>,
}
type PendingMap = Arc<Mutex<HashMap<usize, oneshot::Sender<Result<Value, String>>>>>;
#[derive(Debug)]
pub struct RustAnalyzer {
// Wrapped in a Mutex to support safe concurrent sharing if the design expands
stdin: Arc<Mutex<tokio::process::ChildStdin>>,
pending: PendingMap,
id: usize,
// Keep child handler to explicitly manage child process lifecycle and prevent zombie processes
_child: tokio::process::Child,
}
async fn send_lsp_message<W: AsyncWriteExt + Unpin>(
writer: &mut W,
value: &serde_json::Value,
) -> std::io::Result<()> {
let payload = serde_json::to_string(value)?;
let frame = format!("Content-Length: {}\r\n\r\n{}", payload.len(), payload);
writer.write_all(frame.as_bytes()).await?;
writer.flush().await?;
Ok(())
}
async fn read_lsp_message<R: AsyncBufReadExt + Unpin>(
reader: &mut R,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let mut line = String::new();
let mut content_length = 0;
// Guard against infinite header reading attacks/bugs (Max 100 headers)
for _ in 0..100 {
line.clear();
let bytes_read = reader.read_line(&mut line).await?;
if bytes_read == 0 || line == "\r\n" || line.is_empty() {
break;
}
if line.to_ascii_lowercase().starts_with("content-length:") {
if let Some(val_str) = line.split(':').nth(1) {
content_length = val_str.trim().parse::<usize>()?;
}
}
}
// Explode early if payload size violates strict guard rails to prevent memory-exhaustion (OOM)
if content_length == 0 {
return Err("Missing, invalid, or zero Content-Length header".into());
}
if content_length > MAX_CONTENT_LENGTH {
return Err(format!(
"Content-Length {} exceeds maximum threshold",
content_length
)
.into());
}
// Explicitly secure pre-allocation limit
let mut buffer = vec![0u8; content_length];
reader.read_exact(&mut buffer).await?;
Ok(String::from_utf8(buffer)?)
}
impl RustAnalyzer {
pub fn new() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let mut child = tokio::process::Command::new("rust-analyzer")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?;
let stdin = child
.stdin
.take()
.ok_or("Failed to open child stdin pipe")?;
let stdout = child
.stdout
.take()
.ok_or("Failed to open child stdout pipe")?;
let pending: PendingMap = Arc::new(Mutex::new(HashMap::new()));
let pending_clone = pending.clone();
// Background supervisor task loop
tokio::spawn(async move {
let mut stdout = BufReader::new(stdout);
loop {
let raw = match read_lsp_message(&mut stdout).await {
Ok(v) => v,
Err(err) => {
eprintln!("LSP fatal stream read failure: {err}");
// CRITICAL: Notify all pending channels that the bridge broke down
let mut lock = pending_clone.lock().await;
for (_, tx) in lock.drain() {
let _ = tx.send(Err(format!("LSP reader task dropped: {}", err)));
}
break;
}
};
let value: Value = match serde_json::from_str(&raw) {
Ok(v) => v,
Err(err) => {
eprintln!("Corrupted JSON received: {err}");
continue; // Keep the connection running despite malformed frame
}
};
// Filter server notification frames
if value.get("method").is_some() && value.get("id").is_none() {
continue;
}
let id = value.get("id").and_then(|v| v.as_u64()).map(|v| v as usize);
if let Some(id) = id {
let tx = pending_clone.lock().await.remove(&id);
if let Some(tx) = tx {
let _ = tx.send(Ok(value));
} else {
eprintln!("Received orphaned or delayed frame for ID: {id}");
}
} else {
eprintln!("Received unhandled protocol notification framework: {raw}");
}
}
});
Ok(Self {
stdin: Arc::new(Mutex::new(stdin)),
pending,
id: 0,
_child: child,
})
}
pub async fn request<R: Request>(
&mut self,
params: R::Params,
) -> Result<R::Result, Box<dyn std::error::Error + Send + Sync>>
where
R::Result: DeserializeOwned,
{
let id = {
self.id += 1;
self.id
};
let (tx, rx) = oneshot::channel();
// Scope the lock allocation tightly
{
self.pending.lock().await.insert(id, tx);
}
let payload = json!({
"jsonrpc": "2.0",
"id": id,
"method": R::METHOD,
"params": params,
});
// Acquire lock on writing stream to ensure thread safety
let mut stdin_lock = self.stdin.lock().await;
if let Err(err) = send_lsp_message(&mut *stdin_lock, &payload).await {
// Rollback the pending map operation to avoid internal memory memory-leaks if serialization/IO errors trigger
self.pending.lock().await.remove(&id);
return Err(Box::new(err));
}
// Explicit drop of write lock early so other operations can pipe messages synchronously
drop(stdin_lock);
// Enforce an absolute time constraint limit to break free from hanging processes
let response_payload = match timeout(REQUEST_TIMEOUT, rx).await {
Ok(Ok(Ok(value))) => value,
Ok(Ok(Err(task_err))) => return Err(task_err.into()),
Ok(Err(_oneshot_canceled)) => {
return Err(
"Bridge connection closed down; reader channel dropped unexpectedly".into(),
);
}
Err(_timeout_elapsed) => {
// Clear state tracking entries dynamically upon expiration failure
self.pending.lock().await.remove(&id);
return Err(
format!("Request ID {} timed out after {:?}", id, REQUEST_TIMEOUT).into(),
);
}
};
let envelope: JsonRpcResponse<R::Result> =
serde_json::from_value(response_payload.clone())?;
if let Some(err) = envelope.error {
return Err(format!("LSP Engine Error ({}): {}", err.code, err.message).into());
}
envelope.result.ok_or_else(|| {
format!(
"Missing inner structural payload result: {:?}",
response_payload
)
.into()
})
}
pub async fn notify<T: Serialize>(
&mut self,
method: &str,
req: T,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let payload = json!({
"jsonrpc": "2.0",
"method": method,
"params": req,
});
let mut stdin_lock = self.stdin.lock().await;
send_lsp_message(&mut *stdin_lock, &payload).await?;
Ok(())
}
}
impl RustAnalyzer {
pub async fn initialize(
&mut self,
root: &PathBuf,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let project_uri = Url::from_directory_path(root)
.map_err(|_| "Failed to convert path to valid file:// URL")?;
#[allow(deprecated)]
let init_params = InitializeParams {
process_id: Some(std::process::id()),
root_uri: Some(project_uri.clone()),
workspace_folders: Some(vec![WorkspaceFolder {
uri: project_uri,
name: "workspace".to_string(),
}]),
capabilities: ClientCapabilities {
workspace: Some(lsp_types::WorkspaceClientCapabilities {
workspace_folders: Some(true),
..Default::default()
}),
text_document: Some(lsp_types::TextDocumentClientCapabilities {
..Default::default()
}),
..Default::default()
},
..Default::default()
};
self.request::<request::Initialize>(init_params).await?;
Ok(())
}
pub async fn initialized(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.notify(Initialized::METHOD, InitializedParams {})
.await?;
Ok(())
}
}
+160
View File
@@ -0,0 +1,160 @@
use std::{
fs,
path::{Path, PathBuf},
};
use mist_parser::{ast::TopLevel, error::ParseError};
pub fn build(root: &PathBuf) {
let src_dir = root.join("src");
let out_dir = root.join(".mist/lsp");
if let Err(e) = build_dir(root, &src_dir, &src_dir, &out_dir) {
eprintln!("Warning: Build directory run aborted safely: {e}");
}
}
fn build_dir(
root: &Path,
base_src: &Path,
current_dir: &Path,
out_dir: &Path,
) -> Result<(), String> {
let entries = fs::read_dir(current_dir)
.map_err(|e| format!("failed to read directory {}: {}", current_dir.display(), e))?;
for entry in entries {
let entry = match entry {
Ok(entry) => entry,
Err(e) => {
eprintln!("Warning: Skipping invalid directory entry: {e}");
continue; // Skip corrupted entry instead of crashing
}
};
let path = entry.path();
// Recurse into nested directories
if path.is_dir() {
if let Err(e) = build_dir(root, base_src, &path, out_dir) {
eprintln!("Warning: Nested build directory failed: {e}");
}
continue;
}
// Safe prefix stripping fallback
let relative = match path.strip_prefix(base_src) {
Ok(p) => p,
Err(_) => {
eprintln!(
"Warning: Path {} is outside base source directory",
path.display()
);
continue;
}
};
// Handle non-mist files with a cache check
if path.extension().and_then(|e| e.to_str()) != Some("mist") {
let dest_path = out_dir.join(relative);
// Create parent directories for static assets if needed
if let Some(parent) = dest_path.parent() {
let _ = fs::create_dir_all(parent);
}
if should_skip(&path, &dest_path) {
continue;
}
if let Err(e) = fs::copy(&path, &dest_path) {
eprintln!(
"Warning: Failed to copy non-mist file {}: {}",
path.display(),
e
);
}
continue;
}
let output_path = out_dir.join(relative).with_extension("rs");
// Cache layer: Skip if the generated .rs file is newer than the .mist source
if should_skip(&path, &output_path) {
continue;
}
if let Err(e) = transpile_file(&path, &output_path) {
eprintln!(
"Warning: Transpilation failed for {}: {}",
path.display(),
e
);
}
}
Ok(())
}
pub fn transpile_file(path: &Path, output_path: &Path) -> Result<(), String> {
// Create parent directories
if let Some(parent) = output_path.parent() {
fs::create_dir_all(parent).map_err(|e| {
format!(
"failed to create output directory {}: {}",
parent.display(),
e
)
})?;
}
// Read source
let source = fs::read_to_string(path)
.map_err(|e| format!("failed to read file {}: {}", path.display(), e))?;
let parser_result = mist_parser::parse(&source).map_err(|e| match e {
ParseError::Ast(e) => {
// Using components carefully to prevent out-of-bounds or zero layout crashes
let start_pos = e.span.start_pos().line_col();
let span = e.span.as_str();
format!(
"\n{}:{}:{}\n Error: {}\n\t{}{}\t{}",
path.display(),
start_pos.0,
start_pos.1,
e.error_message,
span,
if span.ends_with('\n') { "" } else { "\n" },
"^".repeat(span.trim().len()),
)
}
ParseError::PreAst(e) => format!("{e}"),
});
// If parsing fails, return the error string back gracefully so the LSP can show it
let ast = parser_result.map_err(|e| format!("parse failed in {}:\n{}", path.display(), e))?;
let mut gc = mist_codegen::RustCodegen::new();
let output = gc.generate(ast);
fs::write(output_path, output)
.map_err(|e| format!("failed to write output {}: {}", output_path.display(), e))?;
Ok(())
}
pub fn transpile_text<'a>(source: &'a str) -> Result<String, ParseError<'a, Vec<TopLevel>>> {
let mut gc = mist_codegen::RustCodegen::new();
Ok(gc.generate(mist_parser::parse(&source)?))
}
fn should_skip(source: &Path, output: &Path) -> bool {
if let (Ok(src_meta), Ok(out_meta)) = (fs::metadata(source), fs::metadata(output)) {
if let (Ok(src_time), Ok(out_time)) = (src_meta.modified(), out_meta.modified()) {
return out_time >= src_time;
}
}
false
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "mist-codegen" name = "mist-codegen"
version = "0.1.1" version = "0.1.7-alpha.0"
edition = "2024" edition = "2024"
description = "The Mist programming language Rust code generator" description = "The Mist programming language Rust code generator"
license = "Apache-2.0" license = "Apache-2.0"
+31 -13
View File
@@ -85,7 +85,7 @@ impl GenRust for Expression {
prefixes, prefixes,
postfixes, postfixes,
} => { } => {
cg.add(&prefixes.get_rust()); prefixes.gen_rust(ctx, cg);
initial.gen_rust(ctx, cg); initial.gen_rust(ctx, cg);
cg.add(&Some(prefixes).get_rust()); cg.add(&Some(prefixes).get_rust());
for postfix in postfixes { for postfix in postfixes {
@@ -110,23 +110,41 @@ impl GenRust for Expression {
} }
} }
impl GetRust for Prefix { impl GenRust for Prefix {
fn get_rust(&self) -> String { fn gen_rust(&self, ctx: &mut Context, cg: &mut RustCodegen) {
match self { match self {
Self::Deref => "*", Self::Deref => cg.add("*"),
Self::Ref => "&", Self::Ref => cg.add("&"),
Self::RefMut => "&mut ", Self::RefMut => cg.add("&mut "),
Self::Not => "!", Self::Not => cg.add("!"),
Self::New(_) => "", Self::New(_) => cg.add(""),
Self::Neg => "-", Self::Neg => cg.add("-"),
Self::Closure(ty, args) => {
cg.add("|");
for (i, arg) in args.iter().enumerate() {
if i > 0 {
cg.add(", ");
}
arg.gen_rust(ctx, cg);
}
cg.add("| ");
if let Some(ty) = ty {
cg.add("-> ");
cg.add(&ty.get_rust());
cg.add(" ");
}
}
} }
.to_string()
} }
} }
impl GetRust for Vec<Prefix> { impl GenRust for Vec<Prefix> {
fn get_rust(&self) -> String { fn gen_rust(&self, ctx: &mut Context, cg: &mut RustCodegen) {
self.into_iter().map(Prefix::get_rust).collect() for prefix in self {
prefix.gen_rust(ctx, cg);
}
} }
} }
+30 -16
View File
@@ -4,10 +4,6 @@ pub mod top_level;
use mist_parser::ast::*; use mist_parser::ast::*;
pub fn get_mutable(mutable: bool) -> String {
if mutable { "mut " } else { "" }.to_string()
}
pub struct Context { pub struct Context {
pub expr_ensure_semicolon: bool, pub expr_ensure_semicolon: bool,
} }
@@ -94,6 +90,17 @@ impl RustCodegen {
} }
} }
} }
pub fn ensure_brackets_body(&mut self, ctx: &mut Context, body: &StatementBody) {
match body {
StatementBody::Expression(expr) => self.ensure_brackets_expr(ctx, expr),
StatementBody::Statement(expr) => {
ctx.expr_ensure_semicolon = true;
self.ensure_brackets_expr(ctx, expr);
}
}
}
} }
impl GenRust for Attribute { impl GenRust for Attribute {
@@ -101,11 +108,12 @@ impl GenRust for Attribute {
match self { match self {
Self::Path(path) => cg.add(&path.get_rust()), Self::Path(path) => cg.add(&path.get_rust()),
Self::NameValue { path, value } => { Self::NameValue { path, value } => {
cg.add(&format!("{} =", path.get_rust())); cg.add(&format!("{} = ", path.get_rust()));
value.gen_rust(ctx, cg); value.gen_rust(ctx, cg);
} }
Self::List { path, items } => { Self::List { path, items } => {
cg.add(&path.get_rust()); cg.add(&path.get_rust());
cg.add("(");
for (i, item) in items.iter().enumerate() { for (i, item) in items.iter().enumerate() {
if i > 0 { if i > 0 {
cg.add(", "); cg.add(", ");
@@ -113,6 +121,7 @@ impl GenRust for Attribute {
item.gen_rust(ctx, cg); item.gen_rust(ctx, cg);
} }
cg.add(")");
} }
} }
} }
@@ -227,34 +236,39 @@ impl GetRust for TypeExprKind {
impl GenRust for Pattern { impl GenRust for Pattern {
fn gen_rust(&self, ctx: &mut Context, cg: &mut RustCodegen) { fn gen_rust(&self, ctx: &mut Context, cg: &mut RustCodegen) {
match self { match self {
Self::Id(id) => cg.add(&id.get_rust()), Self::Path(mutable, path) => {
Self::Path(path) => cg.add(&path.get_rust()), if *mutable {
cg.add("mut ");
};
cg.add(&path.get_rust())
}
Self::Literal(lit) => lit.gen_rust(ctx, cg), Self::Literal(lit) => lit.gen_rust(ctx, cg),
Self::Struct(path, ids) => { Self::Struct(path, inner) => {
cg.add(&path.get_rust()); cg.add(&path.get_rust());
cg.add(" {"); cg.add(" {");
for id in ids { for pat in inner {
cg.add(&id.get_rust()); pat.gen_rust(ctx, cg);
cg.add(","); cg.add(",");
} }
cg.add("}"); cg.add("}");
} }
Self::NamedTuple(path, ids) => { Self::NamedTuple(path, inner) => {
cg.add(&path.get_rust()); cg.add(&path.get_rust());
cg.add(" ("); cg.add(" (");
for id in ids { for pat in inner {
cg.add(&id.get_rust()); pat.gen_rust(ctx, cg);
cg.add(","); cg.add(",");
} }
cg.add(")"); cg.add(")");
} }
Self::Tuple(ids) => { Self::Tuple(inner) => {
cg.add("("); cg.add("(");
for id in ids { for pat in inner {
cg.add(&id.get_rust()); pat.gen_rust(ctx, cg);
cg.add(","); cg.add(",");
} }
cg.add(")"); cg.add(")");
+24 -12
View File
@@ -1,6 +1,6 @@
use mist_parser::ast::*; use mist_parser::ast::*;
use crate::{Context, get_mutable}; use crate::Context;
use crate::{GenRust, GetRust, RustCodegen}; use crate::{GenRust, GetRust, RustCodegen};
@@ -26,6 +26,18 @@ impl GenRust for Block {
} }
} }
impl GenRust for StatementBody {
fn gen_rust(&self, ctx: &mut Context, cg: &mut RustCodegen) {
match self {
Self::Expression(expr) => expr.gen_rust(ctx, cg),
Self::Statement(stmt) => {
ctx.expr_ensure_semicolon = true;
stmt.gen_rust(ctx, cg);
}
}
}
}
impl GenRust for Statement { impl GenRust for Statement {
fn gen_rust(&self, ctx: &mut Context, cg: &mut RustCodegen) { fn gen_rust(&self, ctx: &mut Context, cg: &mut RustCodegen) {
match self { match self {
@@ -47,7 +59,11 @@ impl GenRust for Statement {
cg.add(" {"); cg.add(" {");
cg.indent += 1; cg.indent += 1;
for (pat, body) in match_items { for match_item in match_items {
cg.add_indentedln(&match_item.get_comment());
let MatchItem(pat, body) = &match_item.item;
for (i, p) in pat.iter().enumerate() { for (i, p) in pat.iter().enumerate() {
cg.addln(""); cg.addln("");
cg.add_indented(""); cg.add_indented("");
@@ -76,18 +92,18 @@ impl GenRust for Statement {
cg.add("if "); cg.add("if ");
initial.condition.gen_rust(ctx, cg); initial.condition.gen_rust(ctx, cg);
cg.add(" "); cg.add(" ");
cg.ensure_brackets_expr(ctx, &initial.body); cg.ensure_brackets_body(ctx, &initial.body);
for else_if_branch in else_if { for else_if_branch in else_if {
cg.add("else if"); cg.add("else if");
else_if_branch.condition.gen_rust(ctx, cg); else_if_branch.condition.gen_rust(ctx, cg);
cg.add(" "); cg.add(" ");
cg.ensure_brackets_expr(ctx, &else_if_branch.body); cg.ensure_brackets_body(ctx, &else_if_branch.body);
} }
if let Some(else_br) = else_branch { if let Some(else_br) = else_branch {
cg.add(" else "); cg.add(" else ");
cg.ensure_brackets_expr(ctx, else_br); cg.ensure_brackets_body(ctx, else_br);
} }
} }
@@ -95,12 +111,12 @@ impl GenRust for Statement {
cg.add("while "); cg.add("while ");
condition.gen_rust(ctx, cg); condition.gen_rust(ctx, cg);
cg.add(" "); cg.add(" ");
cg.ensure_brackets_expr(ctx, body); cg.ensure_brackets_body(ctx, body);
} }
Statement::Loop(body) => { Statement::Loop(body) => {
cg.add("loop "); cg.add("loop ");
cg.ensure_brackets_expr(ctx, body); cg.ensure_brackets_body(ctx, body);
} }
Statement::CStyleFor { Statement::CStyleFor {
@@ -146,17 +162,15 @@ impl GenRust for Statement {
} }
Statement::For { Statement::For {
mutable,
pattern, pattern,
iterator, iterator,
body, body,
} => { } => {
cg.add("for "); cg.add("for ");
cg.add(&get_mutable(*mutable));
pattern.gen_rust(ctx, cg); pattern.gen_rust(ctx, cg);
cg.add(" in "); cg.add(" in ");
iterator.gen_rust(ctx, cg); iterator.gen_rust(ctx, cg);
cg.ensure_brackets_expr(ctx, body); cg.ensure_brackets_body(ctx, body);
} }
Statement::Return(expr) => { Statement::Return(expr) => {
@@ -174,8 +188,6 @@ impl GenRust for Statement {
impl GenRust for VarDecl { impl GenRust for VarDecl {
fn gen_rust(&self, ctx: &mut Context, cg: &mut RustCodegen) { fn gen_rust(&self, ctx: &mut Context, cg: &mut RustCodegen) {
cg.add(&get_mutable(self.mutable));
self.name.gen_rust(ctx, cg); self.name.gen_rust(ctx, cg);
cg.add( cg.add(
+40 -2
View File
@@ -212,8 +212,7 @@ impl GenRust for (&Vec<Spanned<FieldDeclStmt>>, &Spanned<ClassConstructor>) {
cg.add_indentedln("}\n"); cg.add_indentedln("}\n");
let mut constructor_params = vec![VarDecl { let mut constructor_params = vec![VarDecl {
mutable: false, name: Pattern::Path(false, Path(vec![Identifier(String::from("self"))])),
name: Pattern::Id(Identifier(String::from("self"))),
type_: Some(TypeExpr( type_: Some(TypeExpr(
TypeExprKind::Path(Path(vec![Identifier(String::from("Self"))])), TypeExprKind::Path(Path(vec![Identifier(String::from("Self"))])),
vec![TypePostfix::RefMut], vec![TypePostfix::RefMut],
@@ -326,6 +325,7 @@ impl GenRust for TopLevelKind {
visibility, visibility,
name, name,
generics, generics,
inherits,
fields, fields,
constructor, constructor,
items, items,
@@ -339,6 +339,12 @@ impl GenRust for TopLevelKind {
)); ));
cg.indent += 1; cg.indent += 1;
if let Some(inherits) = inherits {
cg.add_indented("pub _super: ");
cg.add(&inherits.get_rust());
cg.addln(",");
}
for field in fields.clone() { for field in fields.clone() {
cg.add_indentedln(&field.get_comment()); cg.add_indentedln(&field.get_comment());
cg.add_indentedln(&field.item.decl.get_rust()); cg.add_indentedln(&field.item.decl.get_rust());
@@ -396,6 +402,38 @@ impl GenRust for TopLevelKind {
ClassItem::Method(_) => {} ClassItem::Method(_) => {}
} }
} }
if let Some(inherits) = inherits {
cg.add("impl std::ops::Deref for ");
cg.add(&name.get_rust());
cg.addln(" {");
cg.indent += 1;
cg.add_indented("type Target = ");
cg.add(&inherits.get_rust());
cg.addln(";");
cg.add_indentedln("fn deref(&self) -> &Self::Target {&self._super}");
cg.indent -= 1;
cg.addln("}");
// Mut
cg.add("impl std::ops::DerefMut for ");
cg.add(&name.get_rust());
cg.addln(" {");
cg.indent += 1;
cg.add_indentedln(
"fn deref_mut(&mut self) -> &mut Self::Target {&mut self._super}",
);
cg.indent -= 1;
cg.addln("}");
}
} }
} }
} }
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "mist-api"
version = "0.1.7-alpha.0"
edition = "2024"
[dependencies]
serde = { version = "1.0.228", features = ["serde_derive"] }
serde_json = "1.0.149"
cargo_metadata = "0.23.1"
mist-parser = { workspace = true }
mist-codegen = { workspace = true }
[lib]
name = "mist_api"
path = ".mist/src/lib.rs"
[[bin]]
name = "mist_lsp"
path = ".mist/lsp/main.rs"
@@ -79,7 +79,11 @@ pub fn build(mut args: Vec<String>, root: PathBuf) -> bool {
.expect("Unable to find mapping"); .expect("Unable to find mapping");
let mist_msg = MistDiagnosticMessage { let mist_msg = MistDiagnosticMessage {
message: span.label.clone().unwrap_or(msg.message.message.clone()), message: format!(
"{}: {}",
msg.message.message,
span.label.clone().unwrap_or_default()
),
file_name: if is_root { file_name: if is_root {
mist_file mist_file
} else { } else {
@@ -179,5 +183,5 @@ pub fn get_line(
.collect() .collect()
}); });
lines.get(msg.line).map(|v| v.trim().to_string()) lines.get(msg.line - 1).map(|v| v.trim().to_string())
} }
@@ -1,4 +1,3 @@
pub mod builder; pub mod builder;
pub mod transpiler; pub mod transpiler;
pub mod modules;
fn main() {}
+5
View File
@@ -0,0 +1,5 @@
pub mod builder;
pub mod transpiler;
pub mod modules;
void main() {}
+80
View File
@@ -0,0 +1,80 @@
use <std::path::Path>;
use <std::path::PathBuf>;
use <std::fs>;
use <std::process>;
pub class Module {
pub String name;
pub PathBuf path;
pub Vec<Self> children;
pub constructor(String name, PathBuf mist_path, PathBuf rust_path) {
self.children = new Vec();
self.name = name;
self.path = mist_path;
if (self.path.is_dir()) {
var entries = match (fs::read_dir(&self.path)) {
Ok[entries] => entries;
Err[e] => {
eprintln!(
"error: failed to read directory {}\n {}",
self.path.display(),
e
);
process::exit(1);
}
};
for (entry : entries) {
var entry = match (entry) {
Ok[entry] => entry;
Err[e] => {
eprintln!("error: failed to read directory entry\n {}", e);
process::exit(1);
}
};
PathBuf child_path = entry.path();
String child_name = child_path.file_stem()
.and_then(|var s| s.to_str())
.expect("Failed to get module name")
.to_string();
PathBuf child_output = rust_path.join(&child_name).with_extension("rs");
if (should_skip(&child_path, &child_output)) {
continue;
}
self.children.push(new Module(child_name, child_path, child_output));
}
}
}
}
pub Module new_package(PathBuf mist_path, PathBuf rust_path) {
String name = mist_path.file_stem()
.and_then(|var s| s.to_str())
.expect("Failed to get module name")
.to_string();
new Module(name, mist_path, rust_path)
}
bool should_skip(Path* source, Path* output) {
var src_time = fs::metadata(source)
.ok()
.and_then(|var v| v.modified().ok());
var out_time = fs::metadata(output)
.ok()
.and_then(|var v| v.modified().ok());
out_time
.map(|var out_time| out_time >= src_time.unwrap())
.unwrap_or_default()
}
+162
View File
@@ -0,0 +1,162 @@
use <std::fs>;
use <std::path::Path>;
use <std::path::PathBuf>;
use <std::process>;
use <std::time>;
use <std::env>;
use <mist_parser::error>;
pub PathBuf build(bool force) {
var start = time::Instant::now();
var root = env::current_dir().expect("Unable to find project root");
PathBuf src = root.join("src");
PathBuf out = root.join(".mist/src");
build_dir(&root, &src, &src, &out, force);
var elapsed = start.elapsed();
println!(
"\x1b[32m\nTranspile successful\x1b[0m in \x1b[34m{:.2?}\x1b[0m",
elapsed
);
root
}
void build_dir(Path* root, Path* base_src, Path* current, Path* out, bool force) {
var entries = match (fs::read_dir(current)) {
Ok[entries] => entries;
Err[e] => {
eprintln!(
"error: failed to read directory {}\n {}",
current.display(),
e
);
process::exit(1);
}
};
for (entry : entries) {
var entry = match (entry) {
Ok[entry] => entry;
Err[e] => {
eprintln!("error: failed to read directory entry\n {}", e);
process::exit(1);
}
};
PathBuf path = entry.path();
if (path.is_dir()) {
build_dir(root, base_src, &path, out, force);
continue;
}
Path* relative = path.strip_prefix(base_src).expect("Failed to strip base src from path");
if (path.extension().and_then(|var e| e.to_str()) != Some("mist")) {
PathBuf dest = out.join(relative);
Option<Path*> parent = dest.parent();
if (parent.is_some()) {
fs::create_dir_all(parent.unwrap()).expect("failed to create output directory");
}
if (!force && should_skip(&path, &dest)) {
continue
}
fs::copy(&path, dest).expect("Failed to copy non-mist file");
continue;
}
PathBuf output_file = out.join(relative).with_extension("rs");
if (!force && should_skip(&path, &output_file)) {
continue;
}
Option<Path*> parent = output_file.parent();
if (parent.is_some()) {
fs::create_dir_all(parent.unwrap()).expect("failed to create output directory");
}
String source = match (fs::read_to_string(&path)) {
Ok[s] => s;
Err[e] => {
eprintln!("error: failed to read file {}\n {}", path.display(), e);
process::exit(1);
}
};
var mut gc = mist_codegen::RustCodegen::new();
String output = gc.generate(match (mist_parser::parse(&source)) {
Ok[ast] => ast;
Err[e] => {
match (e) {
error::ParseError::Ast[e] => {
var start_pos = e.span.start_pos().line_col();
var span = e.span.as_str();
eprintln!(
"\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}\n\t{}{}\t{}",
path.as_os_str().display(),
start_pos.0,
start_pos.1,
e.error_message,
span,
if span.ends_with("\n") { "" } else { "\n" },
"^".repeat(span.trim().len()),
);
process::exit(1);
}
error::ParseError::PreAst[e] => {
eprintln!("error: parse failed in {}\n{}", path.display(), e);
process::exit(1);
}
}
}
});
var res = fs::write(&output_file, output);
if (res.is_err()) {
eprintln!(
"error: failed to write output {}\n {}",
output_file.display(),
res.unwrap_err(),
);
process::exit(1);
}
}
}
bool should_skip(Path* source, Path* output) {
var src_time = fs::metadata(source)
.ok()
.and_then(|var v| v.modified().ok());
var out_time = fs::metadata(output)
.ok()
.and_then(|var v| v.modified().ok());
out_time
.map(|var out_time| out_time >= src_time.unwrap())
.unwrap_or_default()
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "mist-parser" name = "mist-parser"
version = "0.1.5" version = "0.1.7-alpha.0"
edition = "2024" edition = "2024"
description = "The Mist programming language parser" description = "The Mist programming language parser"
license = "Apache-2.0" license = "Apache-2.0"
+5 -5
View File
@@ -32,12 +32,11 @@ pub enum Literal {
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub enum Pattern { pub enum Pattern {
NamedTuple(Path, Vec<Identifier>), NamedTuple(Path, Vec<Box<Pattern>>),
Struct(Path, Vec<Identifier>), Struct(Path, Vec<Box<Pattern>>),
Tuple(Vec<Identifier>), Tuple(Vec<Box<Pattern>>),
Literal(Literal), Literal(Literal),
Path(Path), Path(bool, Path),
Id(Identifier),
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
@@ -62,6 +61,7 @@ pub enum Prefix {
New(Option<Generics>), New(Option<Generics>),
Not, Not,
Neg, Neg,
Closure(Option<TypeExpr>, Vec<VarDecl>),
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
+31 -24
View File
@@ -8,29 +8,34 @@ pub struct Block(
pub Option<Spanned<Expression>>, pub Option<Spanned<Expression>>,
); );
#[derive(Debug, Clone, Serialize)]
pub enum StatementBody {
Statement(Expression),
Expression(Expression),
}
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub enum Statement { pub enum Statement {
Block(Block), Block(Block),
If { If {
initial: StatementBranch, initial: StatementBranch,
else_if: Vec<StatementBranch>, else_if: Vec<StatementBranch>,
else_branch: Option<Box<Expression>>, else_branch: Option<StatementBody>,
}, },
Loop(Expression), Loop(StatementBody),
While(StatementBranch), While(StatementBranch),
CStyleFor { CStyleFor {
init: Expression, init: Expression,
condition: Expression, condition: Expression,
update: Expression, update: Expression,
body: Expression, body: StatementBody,
}, },
For { For {
mutable: bool,
pattern: Pattern, pattern: Pattern,
iterator: Expression, iterator: Expression,
body: Box<Expression>, body: StatementBody,
}, },
Match(Expression, Vec<(Vec<Pattern>, Expression)>), Match(Expression, Vec<Spanned<MatchItem>>),
VarDecl(VarDeclStmt), VarDecl(VarDeclStmt),
Return(Option<Expression>), Return(Option<Expression>),
@@ -38,9 +43,11 @@ pub enum Statement {
Continue, Continue,
} }
#[derive(Debug, Clone, Serialize)]
pub struct MatchItem(pub Vec<Pattern>, pub Expression);
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct VarDecl { pub struct VarDecl {
pub mutable: bool,
pub name: Pattern, pub name: Pattern,
pub type_: Option<TypeExpr>, pub type_: Option<TypeExpr>,
} }
@@ -54,28 +61,28 @@ pub struct VarDeclStmt {
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct StatementBranch { pub struct StatementBranch {
pub condition: Expression, pub condition: Expression,
pub body: Box<Expression>, pub body: Box<StatementBody>,
} }
impl Statement { impl Statement {
pub fn is_block(&self) -> bool { pub fn is_block(&self) -> bool {
match self { match self {
Self::Block(_) => true, Self::Block(_)
Self::If { | Self::Match(_, _)
initial, | Self::While(_)
else_if, | Self::For { .. }
else_branch, | Self::Loop(..)
} => { | Self::CStyleFor { .. }
else_branch | Self::If { .. } => true,
.as_ref() _ => false,
.map(|v| v.is_block()) }
.unwrap_or_default() }
|| else_if }
.last()
.map(|b| b.body.is_block()) impl StatementBody {
.unwrap_or_default() pub fn is_soft_return(&self) -> bool {
|| initial.body.is_block() match self {
} Self::Expression(_) => true,
_ => false, _ => false,
} }
} }
+1
View File
@@ -54,6 +54,7 @@ pub enum TopLevelKind {
visibility: Visibility, visibility: Visibility,
name: Identifier, name: Identifier,
generics: GenericsDecl, generics: GenericsDecl,
inherits: Option<TypeExpr>,
fields: Vec<Spanned<FieldDeclStmt>>, fields: Vec<Spanned<FieldDeclStmt>>,
constructor: Spanned<ClassConstructor>, constructor: Spanned<ClassConstructor>,
items: Vec<ClassItem>, items: Vec<ClassItem>,
+39 -20
View File
@@ -91,7 +91,7 @@ generics = { "<" ~ (generic ~ ("," ~ generic)*)? ~ ">" }
// ====================================================== // ======================================================
tuple_type = { tuple_type = {
"(" ~ (type_expr ~ ("," ~ type_expr)*)? ~ ")" "[" ~ (type_expr ~ ("," ~ type_expr)*)? ~ "]"
} }
path_type = { path_type = {
@@ -134,16 +134,24 @@ literal = {
// PATTERNS // PATTERNS
// ====================================================== // ======================================================
multi_pat_template = _{
(pattern ~ ("," ~ pattern)*)?
}
tuple_pattern = { tuple_pattern = {
"[" ~ (identifier ~ ("," ~ identifier)*) ~ "]" "[" ~ multi_pat_template ~ "]"
} }
named_tuple_pattern = { named_tuple_pattern = {
static_path ~ "[" ~ (identifier ~ ("," ~ identifier)*)? ~ "]" static_path ~ "[" ~ multi_pat_template ~ "]"
} }
struct_pattern = { struct_pattern = {
static_path ~ "{" ~ (identifier ~ ("," ~ identifier)*)? ~ "}" static_path ~ "{" ~ multi_pat_template ~ "}"
}
path_pattern = {
mutable? ~ static_path
} }
pattern = _{ pattern = _{
@@ -151,7 +159,7 @@ pattern = _{
| tuple_pattern | tuple_pattern
| struct_pattern | struct_pattern
| literal | literal
| static_path | path_pattern
} }
// ====================================================== // ======================================================
@@ -171,7 +179,7 @@ meta = {
} }
meta_list = { meta_list = {
meta ~ ("," ~ meta)* ~ ","? (meta ~ ("," ~ meta)* ~ ","?)?
} }
attributes = { attributes = {
@@ -182,7 +190,7 @@ attributes = {
// DECLARATION HELPERS // DECLARATION HELPERS
// ====================================================== // ======================================================
var_decl = { var_decl = {
(var | type_expr) ~ mutable? ~ pattern (var | type_expr) ~ pattern
} }
field = { field = {
@@ -190,11 +198,11 @@ field = {
} }
field_list = { field_list = {
field ~ ("," ~ field)* ~ ","? (field ~ ("," ~ field)* ~ ","?)?
} }
param_list = { param_list = {
var_decl ~ ("," ~ var_decl)* ~ ","? (var_decl ~ ("," ~ var_decl)* ~ ","?)?
} }
// ====================================================== // ======================================================
@@ -252,7 +260,7 @@ enum_decl = {
// ====================================================== // ======================================================
class_constructor = { class_constructor = {
visibility? ~ "constructor" ~ generics_decl? ~ "(" ~ param_list? ~ ")" ~ block visibility? ~ "constructor" ~ generics_decl? ~ "(" ~ param_list ~ ")" ~ block
} }
class_fields = { class_fields = {
@@ -268,7 +276,7 @@ self_param = {
} }
method = { method = {
visibility? ~ type_expr ~ identifier ~ generics_decl? ~ "(" ~ self_param? ~ ("," ~ param_list?)? ~ ")" ~ block visibility? ~ type_expr ~ identifier ~ generics_decl? ~ "(" ~ self_param? ~ ("," ~ param_list)? ~ ")" ~ block
} }
class_item = _{ class_item = _{
@@ -277,7 +285,7 @@ class_item = _{
} }
class_decl = { class_decl = {
visibility? ~ "class" ~ identifier ~ generics_decl? ~ "{" ~ class_fields ~ class_constructor ~ class_item* ~ "}" visibility? ~ "class" ~ identifier ~ generics_decl? ~ (":" ~ type_expr)? ~ "{" ~ class_fields ~ class_constructor ~ class_item* ~ "}"
} }
// ====================================================== // ======================================================
@@ -297,7 +305,7 @@ impl_decl = {
trait_requirements = { ":" ~ type_expr_param ~ ("+" ~ type_expr_param)* } trait_requirements = { ":" ~ type_expr_param ~ ("+" ~ type_expr_param)* }
method_no_body = { method_no_body = {
visibility? ~ type_expr ~ identifier ~ generics_decl? ~ "(" ~ self_param? ~ ("," ~ param_list?)? ~ ")" ~ semicolon visibility? ~ type_expr ~ identifier ~ generics_decl? ~ "(" ~ self_param? ~ ("," ~ param_list)? ~ ")" ~ semicolon
} }
trait_item = _{ trait_item = _{
@@ -314,7 +322,7 @@ trait_decl = {
// ====================================================== // ======================================================
function_decl = { function_decl = {
visibility? ~ type_expr ~ identifier ~ "(" ~ param_list? ~ ")" ~ block visibility? ~ type_expr ~ identifier ~ "(" ~ param_list ~ ")" ~ block
} }
// ====================================================== // ======================================================
@@ -343,6 +351,10 @@ statement = _{
| (expr ~ semicolon) | (expr ~ semicolon)
} }
statement_wrapper = { statement }
statement_body = { statement_wrapper | expr }
// ------------------------------------------------------ // ------------------------------------------------------
// BASIC STATEMENTS // BASIC STATEMENTS
// ------------------------------------------------------ // ------------------------------------------------------
@@ -384,7 +396,7 @@ control_flow = {
| block | block
} }
statement_branch = { "(" ~ expr ~ ")" ~ statement } statement_branch = { "(" ~ expr ~ ")" ~ statement_body }
else_if = { else_if = {
"else" ~ "if" ~ statement_branch "else" ~ "if" ~ statement_branch
@@ -395,7 +407,7 @@ else_if_list = {
} }
if_stmt = { if_stmt = {
"if" ~ statement_branch ~ else_if_list ~ ("else" ~ statement)? "if" ~ statement_branch ~ else_if_list ~ ("else" ~ statement_body)?
} }
// ------------------------------------------------------ // ------------------------------------------------------
@@ -407,15 +419,15 @@ while_stmt = {
} }
c_for_stmt = { c_for_stmt = {
"for" ~ "(" ~ statement ~ statement ~ expr ~ ")" ~ statement "for" ~ "(" ~ statement ~ statement ~ expr ~ ")" ~ statement_body
} }
for_stmt = { for_stmt = {
"for" ~ "(" ~ mutable? ~ pattern ~ ":" ~ expr ~ ")" ~ statement "for" ~ "(" ~ pattern ~ ":" ~ expr ~ ")" ~ statement_body
} }
loop_stmt = { loop_stmt = {
"loop" ~ statement "loop" ~ statement_body
} }
// ------------------------------------------------------ // ------------------------------------------------------
@@ -436,6 +448,12 @@ match_stmt = {
// EXPRESSIONS // EXPRESSIONS
// ====================================================== // ======================================================
// ------------------------------------------------------
// CLOSURES
// ------------------------------------------------------
closure = { type_expr? ~ "|" ~ param_list ~ "|" }
// ------------------------------------------------------ // ------------------------------------------------------
// TUPLES & ARRAYS // TUPLES & ARRAYS
// ------------------------------------------------------ // ------------------------------------------------------
@@ -549,7 +567,8 @@ not_px = { "!" }
neg_px = { "-" } neg_px = { "-" }
prefix = { prefix = {
deref_px closure
| deref_px
| mut_ref_px | mut_ref_px
| ref_px | ref_px
| new_px | new_px
+1 -5
View File
@@ -2,8 +2,7 @@ use crate::{
Rule, Rule,
ast::*, ast::*,
ast_expr, ast_expr,
error::{AstError, AstResult, IntoErr}, error::{AstError, IntoErr},
parser::listen_rule,
}; };
impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for VarDeclStmt { impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for VarDeclStmt {
@@ -63,13 +62,10 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for VarDecl {
}) })
.transpose(); .transpose();
let mutable: AstResult<'_, bool> = Ok(listen_rule(&mut inner, Rule::mutable));
let name = Pattern::try_from(inner.next().unwrap()); let name = Pattern::try_from(inner.next().unwrap());
ast_expr!(VarDecl { ast_expr!(VarDecl {
type_: type_, type_: type_,
mutable: mutable,
name: name, name: name,
}) })
} }
+21 -1
View File
@@ -3,6 +3,7 @@ use crate::{
ast::*, ast::*,
ast_ensure, ast_expr, ast_ensure, ast_expr,
error::{AstError, AstResult, GetLength, IntoErr, collect_recovered, collect_recovered_map}, error::{AstError, AstResult, GetLength, IntoErr, collect_recovered, collect_recovered_map},
parser::consume_rule,
}; };
use pest::pratt_parser::PrattParser; use pest::pratt_parser::PrattParser;
use std::sync::OnceLock; use std::sync::OnceLock;
@@ -12,7 +13,7 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Expression {
fn try_from(pair: pest::iterators::Pair<'a, Rule>) -> Result<Self, Self::Error> { fn try_from(pair: pest::iterators::Pair<'a, Rule>) -> Result<Self, Self::Error> {
let rule = pair.as_rule(); let rule = pair.as_rule();
let inner = pair.clone().into_inner(); let mut inner = pair.clone().into_inner();
match rule { match rule {
Rule::expr => { Rule::expr => {
@@ -78,6 +79,15 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Expression {
Rule::static_path => ast_expr!(Expression::Path(pair.try_into())), Rule::static_path => ast_expr!(Expression::Path(pair.try_into())),
Rule::literal => ast_expr!(Expression::Literal(pair.try_into())), Rule::literal => ast_expr!(Expression::Literal(pair.try_into())),
Rule::expr_path => ast_expr!(Expression::Path(pair.try_into())), Rule::expr_path => ast_expr!(Expression::Path(pair.try_into())),
Rule::statement_wrapper => {
let i = inner.next().unwrap();
match i.as_rule() {
Rule::expr => i.try_into(),
_ => ast_expr!(Expression::Statement(
i.try_into().get_map(Box::new).map(Box::new)
)),
}
}
Rule::statement | Rule::basic_stmt | Rule::control_flow => ast_expr!( Rule::statement | Rule::basic_stmt | Rule::control_flow => ast_expr!(
Expression::Statement(pair.try_into().get_map(Box::new).map(Box::new)) Expression::Statement(pair.try_into().get_map(Box::new).map(Box::new))
), ),
@@ -104,6 +114,16 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Prefix {
), ),
Rule::not_px => Self::Not, Rule::not_px => Self::Not,
Rule::neg_px => Self::Neg, Rule::neg_px => Self::Neg,
Rule::closure => {
let mut inner = pair.into_inner();
return ast_expr!(Self::Closure(
consume_rule(&mut inner, Rule::type_expr)
.map(TypeExpr::try_from)
.transpose(),
collect_recovered(inner.next().unwrap().into_inner())
));
}
_ => return AstError::bug_unimplemented(pair), _ => return AstError::bug_unimplemented(pair),
}) })
+11 -8
View File
@@ -7,8 +7,8 @@ use crate::{
Rule, Rule,
ast::*, ast::*,
ast_ensure, ast_expr, ast_ensure, ast_expr,
error::{AstError, IntoErr, collect_recovered}, error::{AstError, AstResult, IntoErr, collect_recovered, collect_recovered_map},
parser::consume_rule, parser::{consume_rule, listen_rule},
}; };
impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Identifier { impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Identifier {
@@ -60,23 +60,26 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Pattern {
let mut inner = pair.clone().into_inner(); let mut inner = pair.clone().into_inner();
match rule { match rule {
Rule::tuple_pattern => ast_expr!(Pattern::Tuple(collect_recovered(pair.into_inner()))), Rule::tuple_pattern => ast_expr!(Pattern::Tuple(collect_recovered_map(inner, |v| {
Self::try_from(v).map(Box::new)
}))),
Rule::named_tuple_pattern => ast_expr!(Pattern::NamedTuple( Rule::named_tuple_pattern => ast_expr!(Pattern::NamedTuple(
Path::try_from(inner.next().unwrap()), Path::try_from(inner.next().unwrap()),
collect_recovered(inner), collect_recovered_map(inner, |v| Self::try_from(v).map(Box::new)),
)), )),
Rule::struct_pattern => ast_expr!(Pattern::Struct( Rule::struct_pattern => ast_expr!(Pattern::Struct(
Path::try_from(inner.next().unwrap()), Path::try_from(inner.next().unwrap()),
collect_recovered(inner), collect_recovered_map(inner, |v| Self::try_from(v).map(Box::new)),
)), )),
Rule::literal => ast_expr!(Pattern::Literal(pair.try_into())), Rule::literal => ast_expr!(Pattern::Literal(pair.try_into())),
Rule::identifier => ast_expr!(Pattern::Id(pair.try_into())), Rule::path_pattern => ast_expr!(Pattern::Path(
Ok(listen_rule(&mut inner, Rule::mutable)) as AstResult<'_, bool>,
Rule::static_path => ast_expr!(Pattern::Path(pair.try_into())), inner.next().unwrap().try_into()
)),
_ => AstError::bug_unimplemented(pair), _ => AstError::bug_unimplemented(pair),
} }
+35 -19
View File
@@ -2,8 +2,7 @@ use crate::{
Rule, Rule,
ast::*, ast::*,
ast_ensure, ast_expr, ast_ensure, ast_expr,
error::{AstError, AstResult, IntoErr, collect_recovered}, error::{AstError, IntoErr, collect_recovered},
parser::listen_rule,
}; };
impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Block { impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Block {
@@ -18,6 +17,24 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Block {
} }
} }
impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for StatementBody {
type Error = AstError<'a, Self>;
fn try_from(pair: pest::iterators::Pair<'a, Rule>) -> Result<Self, Self::Error> {
let mut inner = pair.clone().into_inner();
ast_ensure!(pair, Rule::statement_body => {
let i = inner.next().unwrap();
match i.as_rule() {
Rule::expr => ast_expr!(StatementBody::Expression(i.try_into())),
Rule::statement_wrapper => ast_expr!(StatementBody::Statement(i.try_into())),
_ => AstError::bug_unimplemented(i),
}
})
}
}
impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for StatementBranch { impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for StatementBranch {
type Error = AstError<'a, Self>; type Error = AstError<'a, Self>;
@@ -63,12 +80,7 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Statement {
ast_expr!(Statement::If { ast_expr!(Statement::If {
initial: inner.next().unwrap().try_into(), initial: inner.next().unwrap().try_into(),
else_if: collect_recovered(inner.next().unwrap().into_inner()), else_if: collect_recovered(inner.next().unwrap().into_inner()),
else_branch: inner else_branch: inner.next().map(StatementBody::try_from).transpose(),
.next()
.map(Expression::try_from)
.transpose()
.map(|v| v.map(Box::new))
.get_map(|v| { Some(Box::new(v)) }),
}) })
} }
@@ -84,26 +96,30 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Statement {
}), }),
Rule::for_stmt => ast_expr!(Statement::For { Rule::for_stmt => ast_expr!(Statement::For {
mutable: Ok(listen_rule(&mut inner, Rule::mutable)) as AstResult<'_, bool>,
pattern: inner.next().unwrap().try_into(), pattern: inner.next().unwrap().try_into(),
iterator: inner.next().unwrap().try_into(), iterator: inner.next().unwrap().try_into(),
body: inner.next().unwrap().try_into().map(Box::new), body: inner.next().unwrap().try_into(),
}), }),
Rule::match_stmt => ast_expr!(Statement::Match( Rule::match_stmt => ast_expr!(Statement::Match(
inner.next().unwrap().try_into(), inner.next().unwrap().try_into(),
inner collect_recovered(inner),
.map(|match_itms| {
let mut match_inner = match_itms.into_inner();
Ok((
collect_recovered(match_inner.next().unwrap().into_inner()).get()?,
Expression::try_from(match_inner.next().unwrap()).get()?,
))
})
.collect::<AstResult<'a, Vec<_>>>(),
)), )),
_ => AstError::bug_unimplemented(pair), _ => AstError::bug_unimplemented(pair),
} }
} }
} }
impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for MatchItem {
type Error = AstError<'a, Self>;
fn try_from(pair: pest::iterators::Pair<'a, Rule>) -> Result<Self, Self::Error> {
let mut match_inner = pair.into_inner();
Ok(MatchItem(
collect_recovered(match_inner.next().unwrap().into_inner()).get()?,
Expression::try_from(match_inner.next().unwrap()).get()?,
))
}
}
+2 -2
View File
@@ -28,11 +28,11 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Attribute {
} }
Some(next) => match next.as_rule() { Some(next) => match next.as_rule() {
Rule::primary => { Rule::literal => {
// #[path = literal] // #[path = literal]
Ok(Attribute::NameValue { Ok(Attribute::NameValue {
path, path,
value: inner.next().unwrap().try_into().get()?, value: next.try_into().get()?,
}) })
} }
+1 -4
View File
@@ -23,14 +23,11 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for FunctionDecl {
let self_param = consume_rule(&mut inner, Rule::self_param).map(|param| { let self_param = consume_rule(&mut inner, Rule::self_param).map(|param| {
let mut param_inner = param.into_inner(); let mut param_inner = param.into_inner();
let name = Pattern::Id(Identifier(String::from("self")));
let mutable = listen_rule(&mut param_inner, Rule::mutable); let mutable = listen_rule(&mut param_inner, Rule::mutable);
let is_ref = listen_rule(&mut param_inner, Rule::deref_px); let is_ref = listen_rule(&mut param_inner, Rule::deref_px);
let name = Pattern::Path(mutable && !is_ref, Path(vec![Identifier(String::from("self"))]));
VarDecl { VarDecl {
mutable: mutable && !is_ref,
name: name.clone(), name: name.clone(),
type_: Some(TypeExpr( type_: Some(TypeExpr(
TypeExprKind::Path(Path(vec![Identifier("Self".to_string())])), TypeExprKind::Path(Path(vec![Identifier("Self".to_string())])),
+4
View File
@@ -69,6 +69,10 @@ impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for TopLevelKind {
.transpose() .transpose()
.map(|v| v.unwrap_or_default()), .map(|v| v.unwrap_or_default()),
inherits: consume_rule(&mut inner, Rule::type_expr)
.map(TypeExpr::try_from)
.transpose(),
fields: collect_recovered(inner.next().unwrap().into_inner()), fields: collect_recovered(inner.next().unwrap().into_inner()),
constructor: inner.next().unwrap().try_into(), constructor: inner.next().unwrap().try_into(),
+1
View File
@@ -0,0 +1 @@
package = "main.mist"
+3 -3
View File
@@ -1,7 +1,7 @@
pub enum Event { pub enum Event {
Startup, Startup,
Shutdown, Shutdown,
TaskExecuted(String), TaskExecuted[String],
PluginLoaded(String), PluginLoaded[String],
Error(String), Error[String],
} }
+17 -1
View File
@@ -9,4 +9,20 @@ void main() {
app_instance.boot(); app_instance.boot();
app_instance.run(); app_instance.run();
} }
void do_stuff() {
vec!(1, 2, 3).into_iter().map(Option<i32> |var v| {Some(v)}).collect::<Vec<_>>();
if (43==69) testing1();
}
i32 testing1() {
i32 v = if (0 == 0) {
0
} else {
1
};
v
}
-4
View File
@@ -1,4 +0,0 @@
{
"src": "src",
"output": "build"
}
+3 -4
View File
@@ -1,9 +1,8 @@
use <std::process>; use <std::process>;
use <std::env>; use <std::env>;
use <std::fs>; use <std::fs>;
use <mist_api::builder>;
pub mod builder; use <mist_api::transpiler>;
pub mod transpiler;
void main() { void main() {
Vec<String> args = env::args().collect(); Vec<String> args = env::args().collect();
@@ -102,5 +101,5 @@ void inject_mist_target(bool is_lib) {
"\n[[bin]]\nname = \"my_bin\"\npath = \".mist/src/main.rs\"\n" "\n[[bin]]\nname = \"my_bin\"\npath = \".mist/src/main.rs\"\n"
}; };
fs::write("Cargo.toml", toml_content+inject).expect("Failed to create src/main.mist"); fs::write("Cargo.toml", toml_content+inject).expect("Failed to inject to Cargo.toml");
} }
-166
View File
@@ -1,166 +0,0 @@
use std::{
fs,
path::{Path, PathBuf},
process,
time::Instant,
};
use mist_parser::error::ParseError;
pub fn build(force: bool) -> PathBuf {
let start = Instant::now();
let root = std::env::current_dir()
.ok()
.expect("Unable to find project root");
let src_dir = root.join("src");
let out_dir = root.join(".mist/src");
build_dir(&root, &src_dir, &src_dir, &out_dir, force);
let elapsed = start.elapsed();
println!(
"\x1b[32m\nTranspile successful\x1b[0m in \x1b[34m{:.2?}\x1b[0m",
elapsed
);
root
}
fn build_dir(root: &Path, base_src: &Path, current_dir: &Path, out_dir: &Path, force: bool) {
let entries = match fs::read_dir(current_dir) {
Ok(entries) => entries,
Err(e) => {
eprintln!(
"error: failed to read directory {}\n {}",
current_dir.display(),
e
);
process::exit(1);
}
};
for entry in entries {
let entry = match entry {
Ok(entry) => entry,
Err(e) => {
eprintln!("error: failed to read directory entry\n {}", e);
process::exit(1);
}
};
let path = entry.path();
// recurse into nested directories
if path.is_dir() {
build_dir(root, base_src, &path, out_dir, force);
continue;
}
let relative = path.strip_prefix(base_src).unwrap();
// Handle non-mist files with a cache check
if path.extension().and_then(|e| e.to_str()) != Some("mist") {
let dest_path = out_dir.join(relative);
// Create parent directories for static assets if needed
if let Some(parent) = dest_path.parent() {
let _ = fs::create_dir_all(parent);
}
if !force && should_skip(&path, &dest_path) {
continue;
}
fs::copy(&path, dest_path).expect("Failed to copy non-mist file");
continue;
}
let output_path = out_dir.join(relative).with_extension("rs");
// Cache layer: Skip if the generated .rs file is newer than the .mist source
if !force && should_skip(&path, &output_path) {
continue;
}
// create parent directories
if let Some(parent) = output_path.parent() {
if let Err(e) = fs::create_dir_all(parent) {
eprintln!(
"error: failed to create output directory {}\n {}",
parent.display(),
e
);
process::exit(1);
}
}
// read source
let source = match fs::read_to_string(&path) {
Ok(s) => s,
Err(e) => {
eprintln!("error: failed to read file {}\n {}", path.display(), e);
process::exit(1);
}
};
let parser_result = mist_parser::parse(&source).map_err(|e| match e {
ParseError::Ast(e) => {
let start_pos = e.span.start_pos().line_col();
let span = e.span.as_str();
format!(
"\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}\n\t{}{}\t{}",
path.as_os_str().display(),
start_pos.0,
start_pos.1,
e.error_message,
span,
if span.ends_with("\n") { "" } else { "\n" },
"^".repeat(span.trim().len()),
)
}
ParseError::PreAst(e) => format!("{e}"),
});
let ast = match parser_result {
Ok(ast) => ast,
Err(e) => {
eprintln!("error: parse failed in {}\n{}", path.display(), e);
process::exit(1);
}
};
// semantic::walk_ast(semantic::scope::Scope::from_top(root, &ast), &mut ast);
let mut gc = mist_codegen::RustCodegen::new();
let output = gc.generate(ast);
if let Err(e) = fs::write(&output_path, output) {
eprintln!(
"error: failed to write output {}\n {}",
output_path.display(),
e
);
process::exit(1);
}
}
}
fn should_skip(source: &Path, output: &Path) -> bool {
if let (Ok(src_meta), Ok(out_meta)) = (fs::metadata(source), fs::metadata(output)) {
if let (Ok(src_time), Ok(out_time)) = (src_meta.modified(), out_meta.modified()) {
return out_time >= src_time;
}
}
false
}