diff --git a/Cargo.lock b/Cargo.lock index 25755c1..9fe7b65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,12 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -583,6 +589,16 @@ dependencies = [ "rustpython-parser", ] +[[package]] +name = "pylyzer_wasm" +version = "0.0.60" +dependencies = [ + "erg_common", + "erg_compiler", + "pylyzer_core", + "wasm-bindgen", +] + [[package]] name = "quote" version = "1.0.36" @@ -977,6 +993,61 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.74", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.74", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 3c91f0b..d28b75b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ repository.workspace = true members = [ "crates/py2erg", "crates/pylyzer_core", + "crates/pylyzer_wasm", ] [workspace.package] diff --git a/crates/pylyzer_core/analyze.rs b/crates/pylyzer_core/analyze.rs index 22ca25d..9948c6a 100644 --- a/crates/pylyzer_core/analyze.rs +++ b/crates/pylyzer_core/analyze.rs @@ -6,8 +6,8 @@ use erg_common::traits::{ExitStatus, New, Runnable, Stream}; use erg_common::Str; use erg_compiler::artifact::{BuildRunnable, Buildable, CompleteArtifact, IncompleteArtifact}; use erg_compiler::build_package::GenericPackageBuilder; -use erg_compiler::context::ModuleContext; -use erg_compiler::erg_parser::ast::{Module, AST}; +use erg_compiler::context::{Context, ContextProvider, ModuleContext}; +use erg_compiler::erg_parser::ast::{Module, VarName, AST}; use erg_compiler::erg_parser::build_ast::ASTBuildable; use erg_compiler::erg_parser::error::{ CompleteArtifact as ParseArtifact, IncompleteArtifact as IncompleteParseArtifact, ParseErrors, @@ -16,6 +16,7 @@ use erg_compiler::erg_parser::error::{ use erg_compiler::erg_parser::parse::Parsable; use erg_compiler::error::{CompileError, CompileErrors}; use erg_compiler::module::SharedCompilerResource; +use erg_compiler::varinfo::VarInfo; use erg_compiler::GenericHIRBuilder; use py2erg::{dump_decl_package, ShadowingMode}; use rustpython_ast::source_code::{RandomLocator, SourceRange}; @@ -140,6 +141,18 @@ impl New for PythonAnalyzer { } } +impl ContextProvider for PythonAnalyzer { + fn dir(&self) -> erg_common::dict::Dict<&VarName, &VarInfo> { + self.checker.dir() + } + fn get_receiver_ctx(&self, receiver_name: &str) -> Option<&Context> { + self.checker.get_receiver_ctx(receiver_name) + } + fn get_var_info(&self, name: &str) -> Option<(&VarName, &VarInfo)> { + self.checker.get_var_info(name) + } +} + impl Runnable for PythonAnalyzer { type Err = CompileError; type Errs = CompileErrors; diff --git a/crates/pylyzer_wasm/Cargo.toml b/crates/pylyzer_wasm/Cargo.toml new file mode 100644 index 0000000..39d9060 --- /dev/null +++ b/crates/pylyzer_wasm/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "pylyzer_wasm" +description = "Wasm wrapper for pylyzer" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +publish = false + +[dependencies] +wasm-bindgen = "0.2" +erg_common = { workspace = true } +erg_compiler = { workspace = true } +pylyzer_core = { version = "*", path = "../pylyzer_core" } + +[lib] +crate-type = ["cdylib", "rlib"] +path = "lib.rs" diff --git a/crates/pylyzer_wasm/README.md b/crates/pylyzer_wasm/README.md new file mode 100644 index 0000000..a1d13b5 --- /dev/null +++ b/crates/pylyzer_wasm/README.md @@ -0,0 +1,13 @@ +# pylyzer_wasm + +Wasm wrapper for pylyzer. + +## Usage + +```ts +import { Analyzer } from 'pylyzer_wasm'; + +const analyzer = new Analyzer(); +const errors = analyzer.check('print("Hello, World!")'); +const locals = analyzer.dir(); +``` diff --git a/crates/pylyzer_wasm/lib.rs b/crates/pylyzer_wasm/lib.rs new file mode 100644 index 0000000..e0e5d4e --- /dev/null +++ b/crates/pylyzer_wasm/lib.rs @@ -0,0 +1,262 @@ +use wasm_bindgen::prelude::*; + +use erg_common::error::ErrorCore; +use erg_common::error::Location as Loc; +use erg_common::traits::{Runnable, Stream}; +use erg_compiler::context::ContextProvider; +use erg_compiler::erg_parser::ast::VarName; +use erg_compiler::error::CompileError; +use erg_compiler::ty::Type as Ty; +use erg_compiler::varinfo::VarInfo; +use pylyzer_core::PythonAnalyzer; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[wasm_bindgen] +pub enum CompItemKind { + Method = 0, + Function = 1, + Constructor = 2, + Field = 3, + Variable = 4, + Class = 5, + Struct = 6, + Interface = 7, + Module = 8, + Property = 9, + Event = 10, + Operator = 11, + Unit = 12, + Value = 13, + Constant = 14, + Enum = 15, + EnumMember = 16, + Keyword = 17, + Text = 18, + Color = 19, + File = 20, + Reference = 21, + Customcolor = 22, + Folder = 23, + TypeParameter = 24, + User = 25, + Issue = 26, + Snippet = 27, +} + +#[derive(Debug, Clone)] +#[wasm_bindgen] +pub struct Location(Loc); + +impl From for Location { + fn from(loc: Loc) -> Self { + Self(loc) + } +} + +impl Location { + pub const UNKNOWN: Location = Location(Loc::Unknown); +} + +#[derive(Debug, Clone)] +#[wasm_bindgen] +#[allow(dead_code)] +pub struct Type(Ty); + +#[derive(Debug, Clone)] +#[wasm_bindgen] +pub struct VarEntry { + name: VarName, + vi: VarInfo, +} + +impl VarEntry { + pub fn new(name: VarName, vi: VarInfo) -> Self { + Self { name, vi } + } +} + +#[wasm_bindgen] +impl VarEntry { + pub fn name(&self) -> String { + self.name.to_string() + } + pub fn item_kind(&self) -> CompItemKind { + match &self.vi.t { + Ty::Callable { .. } => CompItemKind::Function, + Ty::Subr(subr) => { + if subr.self_t().is_some() { + CompItemKind::Method + } else { + CompItemKind::Function + } + } + Ty::Quantified(quant) => match quant.as_ref() { + Ty::Callable { .. } => CompItemKind::Function, + Ty::Subr(subr) => { + if subr.self_t().is_some() { + CompItemKind::Method + } else { + CompItemKind::Function + } + } + _ => unreachable!(), + }, + Ty::ClassType => CompItemKind::Class, + Ty::TraitType => CompItemKind::Interface, + Ty::Poly { name, .. } if &name[..] == "Module" => CompItemKind::Module, + _ if self.vi.muty.is_const() => CompItemKind::Constant, + _ => CompItemKind::Variable, + } + } + pub fn typ(&self) -> String { + self.vi.t.to_string() + } +} + +#[wasm_bindgen] +impl Location { + pub fn ln_begin(&self) -> Option { + self.0.ln_begin() + } + + pub fn ln_end(&self) -> Option { + self.0.ln_end() + } + + pub fn col_begin(&self) -> Option { + self.0.col_begin() + } + + pub fn col_end(&self) -> Option { + self.0.col_end() + } +} + +#[derive(Debug, Clone)] +#[wasm_bindgen(getter_with_clone)] +pub struct Error { + pub errno: usize, + pub is_warning: bool, + // pub kind: ErrorKind, + pub loc: Location, + pub desc: String, + pub hint: Option, +} + +fn find_fallback_loc(err: &ErrorCore) -> Loc { + if err.loc == Loc::Unknown { + for sub in &err.sub_messages { + if sub.loc != Loc::Unknown { + return sub.loc; + } + } + Loc::Unknown + } else { + err.loc + } +} + +impl From for Error { + fn from(err: CompileError) -> Self { + let loc = Location(find_fallback_loc(&err.core)); + let sub_msg = err + .core + .sub_messages + .first() + .map(|sub| { + sub.msg + .iter() + .fold("\n".to_string(), |acc, s| acc + s + "\n") + }) + .unwrap_or_default(); + let desc = err.core.main_message + &sub_msg; + Self { + errno: err.core.errno, + is_warning: err.core.kind.is_warning(), + // kind: err.kind(), + loc, + desc, + hint: err + .core + .sub_messages + .first() + .and_then(|sub| sub.hint.clone()), + } + } +} + +impl Error { + pub const fn new( + errno: usize, + is_warning: bool, + loc: Location, + desc: String, + hint: Option, + ) -> Self { + Self { + errno, + is_warning, + loc, + desc, + hint, + } + } +} + +#[wasm_bindgen] +// #[derive()] +pub struct Analyzer { + analyzer: PythonAnalyzer, +} + +impl Default for Analyzer { + fn default() -> Self { + Self::new() + } +} + +#[wasm_bindgen] +impl Analyzer { + pub fn new() -> Self { + Analyzer { + analyzer: PythonAnalyzer::default(), + } + } + + pub fn clear(&mut self) { + self.analyzer.clear(); + } + + pub fn start_message(&self) -> String { + self.analyzer.start_message() + } + + pub fn dir(&mut self) -> Box<[VarEntry]> { + self.analyzer + .dir() + .into_iter() + .map(|(n, vi)| VarEntry::new(n.clone(), vi.clone())) + .collect::>() + .into_boxed_slice() + } + + pub fn check(&mut self, input: &str) -> Box<[Error]> { + match self.analyzer.analyze(input.to_string(), "exec") { + Ok(artifact) => artifact + .warns + .into_iter() + .map(Error::from) + .collect::>() + .into_boxed_slice(), + Err(mut err_artifact) => { + err_artifact.errors.extend(err_artifact.warns); + let errs = err_artifact + .errors + .into_iter() + .map(Error::from) + .collect::>(); + errs.into_boxed_slice() + } + } + } +}