diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 404cd3a..e2ee809 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,14 +7,6 @@ env: RUSTFLAGS: "-Dwarnings" jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Machete - uses: bnjbvr/cargo-machete@main - clippy_check: runs-on: ubuntu-latest steps: diff --git a/Cargo.lock b/Cargo.lock index c4810b6..16a0966 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1126,6 +1126,7 @@ name = "petr-playground" version = "0.1.0" dependencies = [ "petr-api", + "stdlib", "wasm-bindgen", ] @@ -1182,6 +1183,7 @@ dependencies = [ "petr-resolve", "petr-typecheck", "petr-utils", + "stdlib", "thiserror", ] @@ -1361,6 +1363,10 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stdlib" +version = "0.1.0" + [[package]] name = "strsim" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 8a4ccc5..67443ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,4 @@ members = [ "petr-profiling", "petr-api", "petr-playground" -] +, "stdlib"] diff --git a/pete/src/main.rs b/pete/src/main.rs index 853f072..63dd4cc 100644 --- a/pete/src/main.rs +++ b/pete/src/main.rs @@ -7,6 +7,7 @@ use std::{ use clap::Parser as ClapParser; use petr_api::*; use petr_pkg::BuildPlan; +use petr_resolve::Dependency; use termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor}; pub mod error { @@ -189,7 +190,13 @@ pub fn compile( let name = Identifier { id: interner.insert(Rc::from(item.manifest.name)), }; - dependencies.push((item.key, name, item.depends_on, ast)); + + dependencies.push(Dependency { + key: item.key, + name, + dependencies: item.depends_on, + ast, + }); } timings.end("parse dependencies"); @@ -257,25 +264,25 @@ pub fn load_project_and_dependencies(path: &Path) -> Result<(petr_pkg::Lockfile, Ok((lockfile, files, build_plan)) } -pub fn load_files(path: &Path) -> Vec<(PathBuf, String)> { - let mut buf = Vec::new(); - - fn read_petr_files( - dir: &PathBuf, - buf: &mut Vec<(PathBuf, String)>, - ) { - let entries = fs::read_dir(dir).expect("Failed to read directory"); - for entry in entries { - let entry = entry.expect("Failed to read directory entry"); - let path = entry.path(); - if path.is_dir() { - read_petr_files(&path, buf); - } else if path.extension().and_then(|s| s.to_str()) == Some("pt") { - let source = fs::read_to_string(&path).expect("Failed to read file"); - buf.push((path, source)); - } +fn read_petr_files( + dir: &PathBuf, + buf: &mut Vec<(PathBuf, String)>, +) { + let entries = fs::read_dir(dir).expect("Failed to read directory"); + for entry in entries { + let entry = entry.expect("Failed to read directory entry"); + let path = entry.path(); + if path.is_dir() { + read_petr_files(&path, buf); + } else if path.extension().and_then(|s| s.to_str()) == Some("pt") { + let source = fs::read_to_string(&path).expect("Failed to read file"); + buf.push((path, source)); } } +} + +pub fn load_files(path: &Path) -> Vec<(PathBuf, String)> { + let mut buf = Vec::new(); read_petr_files(&path.join("src"), &mut buf); buf diff --git a/petr-api/src/lib.rs b/petr-api/src/lib.rs index 0a01d55..262e935 100644 --- a/petr-api/src/lib.rs +++ b/petr-api/src/lib.rs @@ -1,12 +1,19 @@ //! Top-level API for the petr programming language. //! Exposes relevant APIs from all compiler stages and tooling. -pub use petr_fmt::{format_sources, FormatterConfig}; +#[cfg(not(feature = "no_std"))] +use std::{ + fs, + path::{Path, PathBuf}, + rc::Rc, +}; + +pub use petr_fmt::{format_sources, Formattable, FormatterConfig, FormatterContext}; pub use petr_ir::Lowerer; pub use petr_parse::Parser; #[cfg(not(feature = "no_std"))] pub use petr_pkg::{manifest::find_manifest, BuildPlan}; -pub use petr_resolve::resolve_symbols; +pub use petr_resolve::{resolve_symbols, Dependency}; pub use petr_typecheck::type_check; pub use petr_utils::{render_error, Identifier, IndexMap, SourceId, SpannedItem}; pub use petr_vm::Vm; @@ -149,7 +156,12 @@ pub fn compile( let name = Identifier { id: interner.insert(Rc::from(item.manifest.name)), }; - dependencies.push((item.key, name, item.depends_on, ast)); + dependencies.push(petr_resolve::Dependency { + key: item.key, + name, + dependencies: item.depends_on, + ast, + }); } timings.end("parse dependencies"); diff --git a/petr-ast/src/dependency.rs b/petr-ast/src/dependency.rs new file mode 100644 index 0000000..d8fe390 --- /dev/null +++ b/petr-ast/src/dependency.rs @@ -0,0 +1,14 @@ +use crate::Ast; + +/// Describes the AST of a dependency package +pub struct Dependency { + /// The unique key that identifies this source package, agnostic of the alias given to it in + /// the manifest file + pub key: String, + /// The name of the package as given in the manifest file + pub name: petr_utils::Identifier, + /// The keys of any packages that this package depends on + pub dependencies: Vec, + /// The AST of the source code of this package + pub ast: Ast, +} diff --git a/petr-ast/src/lib.rs b/petr-ast/src/lib.rs index 635b5d0..90039a9 100644 --- a/petr-ast/src/lib.rs +++ b/petr-ast/src/lib.rs @@ -1,5 +1,6 @@ mod ast; mod comments; +pub mod dependency; mod pretty_print; pub use ast::*; pub use comments::*; diff --git a/petr-bind/src/binder.rs b/petr-bind/src/binder.rs index 0823ad2..d7dbbcb 100644 --- a/petr-bind/src/binder.rs +++ b/petr-bind/src/binder.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use petr_ast::{Ast, Binding, ExprId, Expression, FunctionDeclaration, Ty, TypeDeclaration}; +use petr_ast::{dependency::Dependency, Ast, Binding, ExprId, Expression, FunctionDeclaration, Ty, TypeDeclaration}; use petr_utils::{idx_map_key, Identifier, IndexMap, Path, SymbolId}; // TODO: // - i don't know if type cons needs a scope. Might be good to remove that. @@ -102,7 +102,10 @@ impl Scope { k: SymbolId, v: T, ) { - self.items.insert(k, v); + // TODO: error handling and/or shadowing rules for this + if self.items.insert(k, v).is_some() { + todo!("throw error for overriding symbol name {k}") + } } pub fn parent(&self) -> Option { @@ -336,18 +339,17 @@ impl Binder { pub fn from_ast_and_deps( ast: &Ast, - // TODO better type here - dependencies: Vec<( - /* Key */ String, - /*Name from manifest*/ Identifier, - /*Things this depends on*/ Vec, - Ast, - )>, + dependencies: Vec, ) -> Self { let mut binder = Self::new(); - for dependency in dependencies { - let (_key, name, _depends_on, dep_ast) = dependency; + for Dependency { + key: _, + name, + dependencies: _, + ast: dep_ast, + } in dependencies + { let dep_scope = binder.create_scope_from_path(&Path::new(vec![name])); binder.with_specified_scope(dep_scope, |binder, _scope_id| { for module in dep_ast.modules { @@ -397,6 +399,13 @@ impl Binder { ) -> ScopeId { let mut current_scope_id = self.current_scope_id(); for segment in path.iter() { + // if this scope already exists, + // just use that pre-existing ID + if let Some(Item::Module(module_id)) = self.find_symbol_in_scope(segment.id, current_scope_id) { + current_scope_id = self.modules.get(*module_id).root_scope; + continue; + } + let next_scope = self.create_scope(ScopeKind::Module(*segment)); let module = Module { root_scope: next_scope, diff --git a/petr-bind/src/lib.rs b/petr-bind/src/lib.rs index 46557b2..96cca83 100644 --- a/petr-bind/src/lib.rs +++ b/petr-bind/src/lib.rs @@ -3,5 +3,6 @@ //! scopes define. The resolver is then able to do scope-aware name resolution in the next step. pub use binder::{Bind, Binder, BindingId, FunctionId, Item, ModuleId, Scope, ScopeId, ScopeKind, TypeId}; +pub use petr_ast::dependency::Dependency; mod binder; mod impls; diff --git a/petr-fmt/src/lib.rs b/petr-fmt/src/lib.rs index 6d37be3..8b2cbf4 100644 --- a/petr-fmt/src/lib.rs +++ b/petr-fmt/src/lib.rs @@ -15,7 +15,7 @@ use std::{ pub use config::{FormatterConfig, FormatterConfigBuilder}; use constants::{CLOSE_COMMENT_STR, INDENTATION_CHARACTER, OPEN_COMMENT_STR}; -use ctx::FormatterContext; +pub use ctx::FormatterContext; use petr_ast::*; use petr_parse::Parser; use petr_utils::{render_error, PrettyPrint, SpannedItem}; diff --git a/petr-playground/Cargo.toml b/petr-playground/Cargo.toml index a7b440c..2256b9c 100644 --- a/petr-playground/Cargo.toml +++ b/petr-playground/Cargo.toml @@ -11,3 +11,4 @@ crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2.92" petr-api = { path = "../petr-api", default-features = false, features = ["no_std"] } +stdlib = { path = "../stdlib" } diff --git a/petr-playground/index.html b/petr-playground/index.html index 06a3123..dabadb2 100644 --- a/petr-playground/index.html +++ b/petr-playground/index.html @@ -25,6 +25,7 @@ #output { /* monospace font, smallish font */ font-family: monospace; + white-space: pre; font-size: 0.8em; } diff --git a/petr-playground/index.js b/petr-playground/index.js index 95c3d56..090f076 100644 --- a/petr-playground/index.js +++ b/petr-playground/index.js @@ -1,10 +1,9 @@ -import { run_snippet } from './pkg'; +import { run_snippet, format } from './pkg'; import * as monaco from 'monaco-editor'; // or import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; // if shipping only a subset of the features & languages is desired -// // config for petr as a custom language // // Register a new language @@ -15,7 +14,7 @@ monaco.languages.setMonarchTokensProvider("petr", { keywords: [ 'function', 'returns', 'in' ], tokenizer: { root: [ - [/\~[a-zA-Z][a-zA-Z0-9]*/, "function-call"], + [/\~([a-zA-Z][a-zA-Z0-9]+)(\.[a-zA-Z]([a-zA-Z0-9])+)*/, "function-call"], [/\@[a-zA-Z]+/, "intrinsic"], [/[0-9]+/, "integer-literal"], [/\".*\"/, "string-literal"], @@ -31,13 +30,13 @@ monaco.languages.setLanguageConfiguration("petr", { autoClosingPairs: [ { open: '[', close: ']' }, { open: '(', close: ')' }, - { open: "'", close: "'" } + { open: '"', close: '"' } ], surroundingPairs: [ { open: '{', close: '}' }, { open: '[', close: ']' }, { open: '(', close: ')' }, - { open: "'", close: "'" } + { open: '"', close: '"' } ] }) @@ -52,6 +51,7 @@ monaco.editor.defineTheme("petr-theme", { { token: "function-call", foreground: "808080", fontStyle: "bold" }, { token: "string-literal", foreground: literalColor }, { token: "integer-literal", foreground: literalColor }, + { token: "keyword", foreground: literalColor }, ], colors: { "editor.foreground": "#ffffff", @@ -111,7 +111,7 @@ monaco.languages.registerCompletionItemProvider("mySpecialLanguage", { monaco.editor.create(document.getElementById('monaco-editor'), { - value: "function main() returns 'unit \n @puts(\"Hello, World!\")", + value: "function main() returns 'unit \n ~std.io.print \"Hello, World!\"", language: 'petr', theme: "petr-theme", }); @@ -121,6 +121,12 @@ export function setOutputContent(content) { } window.setOutputContent = setOutputContent; +export function setCodeEditorContent(content) { + monaco.editor.getModels()[0].setValue(content); +} + +window.setCodeEditorContent= setCodeEditorContent; + // set on-clicks for the buttons document.getElementById('run').onclick = function() { // get the text content from the monaco instance @@ -137,3 +143,16 @@ document.getElementById('run').onclick = function() { return; }; } +document.getElementById('format').onclick = function() { + // get the text content from the monaco instance + let code = monaco.editor.getModels()[0].getValue(); + // run the code + // TODO: actually render the errors on the span in the diagnostics of monaco + try { format(code); } catch (e) { + // set the output to the diagnostics + // because an Err result from wasm becomes an exception + // might be good to not use Result for that reason + document.getElementById('output').innerHTML = e; + return; + }; +} diff --git a/petr-playground/src/lib.rs b/petr-playground/src/lib.rs index ee8dd97..838698e 100644 --- a/petr-playground/src/lib.rs +++ b/petr-playground/src/lib.rs @@ -2,15 +2,20 @@ //! Nothing fancy at all, could definitely be improved over time to support better error reporting, //! etc +use std::rc::Rc; + use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = setOutputContent)] fn set_output_content(s: &str); + + #[wasm_bindgen(js_name = setCodeEditorContent)] + fn set_code_editor_content(s: &str); } -use petr_api::{render_error, resolve_symbols, type_check, FormatterConfig, Lowerer, Parser, Vm}; +use petr_api::{render_error, resolve_symbols, type_check, Dependency, Formattable, FormatterContext, Identifier, Lowerer, Parser, Vm}; #[wasm_bindgen] pub fn run_snippet(code: &str) { @@ -26,7 +31,7 @@ pub fn run_snippet(code: &str) { let (data, instructions) = lowerer.finalize(); let vm = Vm::new(instructions, data); - let result = match vm.run() { + let (result, _stack, logs) = match vm.run() { Ok(o) => o, Err(e) => { set_output_content(&format!("Runtime error: {:#?}", e)); @@ -34,7 +39,7 @@ pub fn run_snippet(code: &str) { }, }; - set_output_content(&format!("Result: {:#?}", result)); + set_output_content(&format!("Logs:
\t{}
Result:
\t{:#?}", logs.join("\n\t"), result.inner())); } fn errors_to_html(e: &[String]) -> String { @@ -51,11 +56,21 @@ fn compile_snippet(code: String) -> Result> { let buf = vec![("snippet".to_string(), code)]; let mut errs = vec![]; let parser = Parser::new(buf); - // TODO include standard library in WASM compilation target and - // bring it in to this repo - let dependencies = vec![]; - let (ast, parse_errs, interner, source_map) = parser.into_result(); + let (ast, mut parse_errs, interner, source_map) = parser.into_result(); + // add the standard library to the sources + let parser = Parser::new_with_existing_interner_and_source_map(stdlib::stdlib(), interner, source_map); + let (dep_ast, mut new_parse_errs, mut interner, source_map) = parser.into_result(); + parse_errs.append(&mut new_parse_errs); + let dependencies = vec![Dependency { + key: "stdlib".to_string(), + name: Identifier { + id: interner.insert(Rc::from("std")), + }, + dependencies: vec![], + ast: dep_ast, + }]; + // TODO after diagnostics are implemented for these errors, append them to the errors and // return them let (_resolution_errs, resolved) = resolve_symbols(ast, interner, dependencies); @@ -71,9 +86,18 @@ fn compile_snippet(code: String) -> Result> { } } -pub fn format( - _code: String, - _config: FormatterConfig, -) -> Result { - todo!() +#[wasm_bindgen] +pub fn format(code: String) { + let parser = Parser::new(vec![("snippet".to_string(), code)]); + let (ast, errs, interner, source_map) = parser.into_result(); + if !errs.is_empty() { + let errs = errs + .into_iter() + .map(|e| format!("{:?}", render_error(&source_map, e))) + .collect::>(); + set_output_content(&errs.join("
")); + } + let mut ctx = FormatterContext::from_interner(interner).with_config(Default::default()); + let formatted_content = ast.line_length_aware_format(&mut ctx).render(); + set_code_editor_content(&formatted_content); } diff --git a/petr-resolve/src/lib.rs b/petr-resolve/src/lib.rs index d9d9da2..bd4ce4e 100644 --- a/petr-resolve/src/lib.rs +++ b/petr-resolve/src/lib.rs @@ -1,9 +1,9 @@ //! given bindings, fully resolve an AST //! This crate's job is to tee up the type checker for the next stage of compilation. -use petr_ast::Ast; pub use petr_ast::{Intrinsic as IntrinsicName, Literal, Ty}; -use petr_utils::{Identifier, SymbolInterner}; +pub use petr_bind::Dependency; +use petr_utils::SymbolInterner; pub use resolved::QueryableResolvedItems; use resolver::Resolver; pub use resolver::{Expr, ExprKind, Function, FunctionCall, Intrinsic, ResolutionError, Type}; @@ -14,13 +14,8 @@ mod resolver; pub fn resolve_symbols( ast: petr_ast::Ast, interner: SymbolInterner, - // TODO better type here - dependencies: Vec<( - /* Key */ String, - /*Name from manifest*/ Identifier, - /*Things this depends on*/ Vec, - Ast, - )>, + // TODO refactor tuple into struct with named fields + dependencies: Vec, ) -> (Vec, QueryableResolvedItems) { let resolver = Resolver::new(ast, interner, dependencies); resolver.into_queryable() diff --git a/petr-resolve/src/resolver.rs b/petr-resolve/src/resolver.rs index 70ce95a..08a0a8e 100644 --- a/petr-resolve/src/resolver.rs +++ b/petr-resolve/src/resolver.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use petr_ast::{Ast, Commented, Expression, FunctionDeclaration, FunctionParameter, OperatorExpression}; -use petr_bind::{Binder, FunctionId, Item, ScopeId, TypeId}; +use petr_bind::{Binder, Dependency, FunctionId, Item, ScopeId, TypeId}; use petr_utils::{Identifier, Path, SpannedItem, SymbolInterner}; use thiserror::Error; @@ -59,8 +59,13 @@ impl Resolve for petr_ast::Ty { petr_ast::Ty::Unit => Type::Unit, petr_ast::Ty::Named(name) => match binder.find_symbol_in_scope(name.id, scope_id) { Some(Item::Type(id)) => Type::Named(*id), - Some(_) => { - todo!("push error -- symbol is not type"); + Some(something) => { + let name = _resolver.interner.get(name.id); + // this makes sense, the type constructor is getting resolved instead of the ty + // find_symbol_in_scope could take in what it is looking for as a parameter, + // _or_ we could have a special case when a function body is just a type + // constructorjkkj + todo!("push error -- symbol {name} is not type, it is a {something:?}"); // return None; }, None => Type::Generic(*name), @@ -144,13 +149,7 @@ impl Resolver { pub fn new( ast: Ast, interner: SymbolInterner, - // TODO better type here - dependencies: Vec<( - /* Key */ String, - /*Name from manifest*/ Identifier, - /*Things this depends on*/ Vec, - Ast, - )>, + dependencies: Vec, ) -> Self { let binder = Binder::from_ast_and_deps(&ast, dependencies); let mut resolver = Self { @@ -190,7 +189,15 @@ impl Resolver { }, Binding(a) => { let binding = binder.get_binding(*a); - let resolved_expr = binding.val.resolve(self, binder, scope_id).expect("TODO err"); + let resolved_expr = match binding.val.resolve(self, binder, scope_id) { + Some(o) => o, + None => { + // TODO i think this is incorrect + let name = self.interner.get(binding.name.id); + self.errs.push(ResolutionError::NotFound(name.to_string())); + return; + }, + }; self.resolved.bindings.insert( *a, crate::resolver::Binding { diff --git a/petr-vm/Cargo.toml b/petr-vm/Cargo.toml index 4290061..0abba4b 100644 --- a/petr-vm/Cargo.toml +++ b/petr-vm/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] petr-ir = { path = "../petr-ir" } -petr-utils = { path = "../petr-utils" } +petr-utils = { path = "../petr-utils", optional = true } thiserror = "1.0.61" [dev-dependencies] @@ -13,3 +13,8 @@ petr-parse = { path = "../petr-parse" } expect-test = "1.5.0" petr-resolve = { path = "../petr-resolve" } petr-typecheck = { path = "../petr-typecheck" } +stdlib = { path = "../stdlib" } + +[features] +debug = ["petr-utils/debug"] +default = ["dep:petr-utils"] diff --git a/petr-vm/src/lib.rs b/petr-vm/src/lib.rs index fb6f333..8849077 100644 Binary files a/petr-vm/src/lib.rs and b/petr-vm/src/lib.rs differ diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml new file mode 100644 index 0000000..c4c0e7f --- /dev/null +++ b/stdlib/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "stdlib" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/stdlib/pete.toml b/stdlib/pete.toml new file mode 100644 index 0000000..7a68f6e --- /dev/null +++ b/stdlib/pete.toml @@ -0,0 +1,8 @@ +author = "Alex Hansen " +license = "MIT" +name = "test_project" + +[formatter] + +[dependencies.std] +git = "https://github.com/sezna/petr-std" diff --git a/stdlib/petr.lock b/stdlib/petr.lock new file mode 100644 index 0000000..f572e7d --- /dev/null +++ b/stdlib/petr.lock @@ -0,0 +1 @@ +entries = [] diff --git a/stdlib/src/io.pt b/stdlib/src/io.pt new file mode 100644 index 0000000..7a02e2a --- /dev/null +++ b/stdlib/src/io.pt @@ -0,0 +1,3 @@ + +Function print(content in 'string) returns 'unit + @puts content diff --git a/stdlib/src/lib.rs b/stdlib/src/lib.rs new file mode 100644 index 0000000..7446c86 --- /dev/null +++ b/stdlib/src/lib.rs @@ -0,0 +1,4 @@ +/// returns all files in the standard library +pub fn stdlib() -> Vec<(&'static str, &'static str)> { + vec![("std/ops.pt", include_str!("ops.pt")), ("std/io.pt", include_str!("io.pt"))] +} diff --git a/stdlib/src/ops.pt b/stdlib/src/ops.pt new file mode 100644 index 0000000..cdee3f2 --- /dev/null +++ b/stdlib/src/ops.pt @@ -0,0 +1,7 @@ +Function add(lhs in 'int, rhs in 'int) returns 'int @add lhs, rhs + +Function sub(lhs in 'int, rhs in 'int) returns 'int @subtract lhs, rhs + +Function mult(lhs in 'int, rhs in 'int) returns 'int @multiply lhs, rhs + +Function div(lhs in 'int, rhs in 'int) returns 'int @divide lhs, rhs