diff --git a/Cargo.lock b/Cargo.lock
index 21dab37..b97567f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -61,6 +61,18 @@ dependencies = [
"windows-sys",
]
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
[[package]]
name = "colorchoice"
version = "1.0.4"
@@ -132,6 +144,12 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
@@ -200,6 +218,12 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
[[package]]
name = "serde_core"
version = "1.0.228"
@@ -227,6 +251,7 @@ dependencies = [
"env_logger",
"log",
"thiserror",
+ "wasm-bindgen",
]
[[package]]
@@ -272,6 +297,51 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
+dependencies = [
+ "unicode-ident",
+]
+
[[package]]
name = "windows-link"
version = "0.2.1"
diff --git a/Cargo.toml b/Cargo.toml
index e991604..9338f3e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,3 +1,11 @@
+[lib]
+crate-type = ["cdylib"]
+path = "src/lib.rs"
+
+[[bin]]
+name = "spemath-cli"
+path = "src/main.rs"
+
[package]
name = "spemath"
version = "0.1.0"
@@ -6,4 +14,5 @@ edition = "2024"
[dependencies]
log = "0.4"
env_logger = "0.11.8"
-thiserror = "2.0.17"
\ No newline at end of file
+thiserror = "2.0.17"
+wasm-bindgen = "0.2"
diff --git a/README.md b/README.md
index f6cbbc4..a5f0712 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,6 @@
Spemath est un langage de programmation éducatif inspiré du programme de spécialité mathématiques.
Il est conçu pour être fidèle aux conventions mathématiques enseignées en première ainsi que celles en terminale (bientôt!)
-
-
## Syntaxe de base
Voir la [documentation](docs/syntax.md)
-
-TODO: Flags
\ No newline at end of file
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..e28ed95
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ Spemath
+
+
+
+
+ Welcome to Spemath
+ Spemath is a programming language designed for educational purposes, inspired by the high-school mathematics.
+ See Syntax for more information.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/main.js b/docs/main.js
new file mode 100644
index 0000000..73f8a16
--- /dev/null
+++ b/docs/main.js
@@ -0,0 +1,20 @@
+import init, { run_code } from "./pkg/spemath.js";
+
+async function main() {
+ await init();
+
+ const input = document.getElementById("input");
+ const output = document.getElementById("output");
+ const run = document.getElementById("run");
+
+ run.addEventListener("click", () => {
+ try {
+ const result = run_code(input.value);
+ output.textContent = result;
+ } catch (err) {
+ output.textContent = "WASM Error: " + err;
+ }
+ });
+}
+
+main();
diff --git a/docs/spemath.ebnf b/docs/spemath.ebnf
deleted file mode 100644
index 88de055..0000000
--- a/docs/spemath.ebnf
+++ /dev/null
@@ -1,32 +0,0 @@
-# TODO
-## EBNF Grammar
-
-program = { expression ";" } ;
-
-expression = assignment
- | sum ;
-
-### Assignments
-assignment = identifier "=" expression
- | identifier "(" identifier { "," identifier } ")" "=" expression ;
- # TODO: Suites
-
-### Arithmetic Expressions
-sum = product { ("+" | "-") product } ;
-product = power { ("*" | "/" | "%") power } ;
-power = unary { "^" unary } ;
-unary = ["+" | "-"] primary ;
-
-### Primary Expressions
-primary = number
- | identifier
- | function_call
- | "(" expression ")" ;
-
-function_call = identifier "(" [ expression { "," expression } ] ")" ;
-
-### Literals and identifiers
-number = digit { digit } [ "." digit { digit } ] ;
-identifier = letter { letter | digit | "_" } ;
-letter = "A"..."Z" | "a"..."z" ;
-digit = "0"..."9" ;
diff --git a/docs/styles.css b/docs/styles.css
new file mode 100644
index 0000000..d879d3d
--- /dev/null
+++ b/docs/styles.css
@@ -0,0 +1,97 @@
+:root{
+ --bg:#0f0f0f;
+ --panel:#111;
+ --muted:#1b1b1b;
+ --accent:#7c3aed;
+ --accent-hover :#9d4edd;
+ --text:#ffffff;
+ --highlight:#40c057;
+ --max-width:900px;
+ --gutter:20px;
+ --radius:4px;
+ --input-height:150px;
+ --font-sans: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", "Courier New", monospace;
+}
+
+* { box-sizing: border-box; }
+
+html, body {
+ height: 100%;
+ margin: 0;
+ background: var(--bg);
+ color: var(--text);
+ font-family: var(--font-sans);
+ -webkit-font-smoothing:antialiased;
+ -moz-osx-font-smoothing:grayscale;
+ padding: 0;
+}
+
+body {
+ padding: var(--gutter);
+}
+
+.container {
+ max-width: var(--max-width);
+ margin: 48px auto;
+ padding: var(--gutter);
+}
+
+h1 {
+ margin: 0 0 18px 0;
+ font-size: 1.6rem;
+ line-height: 1.2;
+}
+
+textarea,
+button,
+pre {
+ display: block;
+ width: 100%;
+ border-radius: var(--radius);
+ font-size: 16px;
+}
+
+textarea {
+ height: var(--input-height);
+ background: var(--muted);
+ color: var(--text);
+ border: 1px solid #333;
+ padding: 12px;
+ font-family: var(--font-mono);
+ resize: vertical;
+}
+
+button {
+ margin-top: 15px;
+ padding: 12px;
+ background: var(--accent);
+ border: none;
+ color: var(--text);
+ font-size: 18px;
+ cursor: pointer;
+ transition: background 160ms ease;
+ border-radius: calc(var(--radius) + 2px);
+}
+
+button:hover {
+ background: var(--accent-hover);
+}
+
+pre {
+ background: var(--panel);
+ color: var(--highlight);
+ padding: 15px;
+ margin-top: 20px;
+ min-height: var(--input-height);
+ border-radius: var(--radius);
+ white-space: pre-wrap;
+ border: 1px solid #222;
+ overflow: auto;
+}
+
+@media (max-width: 520px) {
+ .container { margin: 24px auto; padding: 14px; }
+ h1 { font-size: 1.25rem; }
+ button { font-size: 16px; padding: 10px; }
+}
diff --git a/docs/syntax.md b/docs/syntax.md
index e931daa..dbed504 100644
--- a/docs/syntax.md
+++ b/docs/syntax.md
@@ -1,3 +1,136 @@
-# Syntaxe
+# Syntax
-Work in progress!
+## 1. Numbers
+
+```text
+123
+3.14
+-42
++7
+```
+
+* Supports integers and floating-point numbers.
+* Unary plus (`+`) and minus (`-`) are allowed.
+
+---
+
+## 2. Identifiers
+
+```text
+x
+y
+myVar
+f
+```
+
+* Can be used for variables and function names.
+* Must start with a letter or underscore, followed by letters, digits, or underscores.
+
+---
+
+## 3. Assignments
+
+```text
+x = 5
+y = x + 3
+f(x) = x * 2
+```
+
+* Use `=` to assign values to variables.
+* Functions can be defined inline using the syntax: `f(param1, param2) = expression`.
+
+---
+
+## 4. Arithmetic Operators
+
+| Operator | Description | Example |
+| -------- | -------------- | -------- |
+| `+` | Addition | `1 + 2` |
+| `-` | Subtraction | `5 - 3` |
+| `*` | Multiplication | `2 * 4` |
+| `/` | Division | `10 / 2` |
+| `%` | Modulo | `10 % 3` |
+| `^` | Exponentiation | `2 ^ 3` |
+
+* Multiplication can be **implicit**:
+
+ ```text
+ 2x // interpreted as 2 * x
+ 3(x+1) // interpreted as 3 * (x + 1)
+ ```
+
+---
+
+## 5. Comparison Operators
+
+| Operator | Description | Example |
+| -------- | ---------------- | -------- |
+| `==` | Equal | `x == 5` |
+| `!=` | Not equal | `x != 0` |
+| `<` | Less than | `x < 10` |
+| `>` | Greater than | `x > 10` |
+| `<=` | Less or equal | `x <= 5` |
+| `>=` | Greater or equal | `x >= 5` |
+
+---
+
+## 6. Grouping
+
+```text
+(x + y) * z
+```
+
+* Parentheses `()` are used to group expressions and override operator precedence.
+
+---
+
+## 7. Function Calls
+
+```text
+f(2, 3)
+sqrt(16)
+```
+
+* Functions are called with parentheses.
+* No whitespace is allowed between the function name and `(` for proper parsing.
+
+---
+
+## 8. Implicit Multiplication
+
+```text
+2x // equivalent to 2 * x
+(x+1)(y-1) // equivalent to (x+1) * (y-1)
+```
+
+* Implicit multiplication works when a number or closing parenthesis is followed immediately by an identifier or another parenthesis.
+
+---
+
+## 9. Expressions
+
+* Expressions can be separated by:
+
+ * Semicolon `;`
+ * Newline
+
+```text
+x = 5
+y = x + 3; z = y^2
+```
+
+---
+
+## 10. Comments
+
+* Doesn't have any effects but they exist
+
+ ```text
+ // This is a comment
+ ```
+
+ ```text
+ /*
+ This is a multi-line comment
+ */
+ ```
diff --git a/scripts/pack.ps1 b/scripts/pack.ps1
new file mode 100644
index 0000000..8a9cd5c
--- /dev/null
+++ b/scripts/pack.ps1
@@ -0,0 +1 @@
+wasm-pack build --target web --out-dir ./docs/pkg
diff --git a/src/core/mod.rs b/src/core/mod.rs
new file mode 100644
index 0000000..42e9c72
--- /dev/null
+++ b/src/core/mod.rs
@@ -0,0 +1 @@
+pub mod runtime;
\ No newline at end of file
diff --git a/src/core/runtime.rs b/src/core/runtime.rs
new file mode 100644
index 0000000..364d594
--- /dev/null
+++ b/src/core/runtime.rs
@@ -0,0 +1,38 @@
+use crate::lexer::tokenizer::Lexer;
+use crate::parser::pratt::Parser;
+use crate::interpreter::eval::Evaluator;
+use crate::interpreter::value::Value;
+
+pub fn run_source(source: &str) -> Result {
+ // 1. LEXER
+ let mut lexer = Lexer::new(source);
+ let tokens = lexer.tokenize().map_err(|errs| {
+ errs.into_iter()
+ .map(|e| e.to_string())
+ .collect::>()
+ .join("\n")
+ })?;
+
+ // 2. PARSER
+ let mut parser = Parser::new(tokens);
+ let exprs = parser.parse().map_err(|errs| {
+ errs.into_iter()
+ .map(|e| e.to_string())
+ .collect::>()
+ .join("\n")
+ })?;
+
+ // 3. EVALUATOR
+ let mut evaluator = Evaluator::new();
+ let mut output = String::new();
+
+ for expr in exprs {
+ match evaluator.eval(&expr) {
+ Ok(Value::Unit) => {}
+ Ok(value) => output.push_str(&format!("{:?}\n", value)),
+ Err(err) => output.push_str(&format!("Runtime Error: {}\n", err)),
+ }
+ }
+
+ Ok(output)
+}
diff --git a/src/interpreter/env.rs b/src/interpreter/env.rs
index 34a9da2..0c85acb 100644
--- a/src/interpreter/env.rs
+++ b/src/interpreter/env.rs
@@ -21,4 +21,10 @@ impl Env {
pub fn set(&mut self, name: String, value: Value) {
self.variables.insert(name, value);
}
+}
+
+impl Default for Env {
+ fn default() -> Self {
+ Self::new()
+ }
}
\ No newline at end of file
diff --git a/src/interpreter/eval.rs b/src/interpreter/eval.rs
index f195974..a30fe66 100644
--- a/src/interpreter/eval.rs
+++ b/src/interpreter/eval.rs
@@ -103,3 +103,9 @@ impl Evaluator {
}
}
}
+
+impl Default for Evaluator {
+ fn default() -> Self {
+ Self::new()
+ }
+}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..dfd70fb
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,16 @@
+use wasm_bindgen::prelude::*;
+
+pub mod core;
+pub mod lexer;
+pub mod parser;
+pub mod interpreter;
+
+use crate::core::runtime::run_source;
+
+#[wasm_bindgen]
+pub fn run_code(source: &str) -> String {
+ match run_source(source) {
+ Ok(output) => output,
+ Err(err) => format!("Error: {}", err),
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 1bfe4be..5b78f12 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,57 +1,50 @@
use std::fs;
+mod interpreter;
+mod lexer;
+mod parser;
+
+
use crate::interpreter::eval::Evaluator;
use crate::interpreter::value::Value;
use crate::lexer::tokenizer::Lexer;
use crate::parser::pratt::Parser;
-mod interpreter;
-mod lexer;
-mod parser;
fn main() {
env_logger::init();
- let source = fs::read_to_string("input.spemath").expect("Could not read input.spemath");
- log::info!("Starting lexer...");
+ let source = fs::read_to_string("input.spemath")
+ .expect("Could not read input.spemath");
+
let mut lexer = Lexer::new(&source);
let tokens = match lexer.tokenize() {
- Ok(tokens) => {
- log::info!("Lexer produced {} token(s)", tokens.len());
- tokens
- }
+ Ok(tokens) => tokens,
Err(errors) => {
- log::info!("Lexer encountered {} error(s)", errors.len());
for err in errors {
- log::error!("{:?}", err);
+ eprintln!("{:?}", err);
}
return;
}
};
- log::debug!("Tokens: {:#?}", tokens);
-
- log::info!("Starting parser...");
let mut parser = Parser::new(tokens);
- match parser.parse() {
- Ok(exprs) => {
- log::info!("Parser produced {} expression(s)", exprs.len());
- log::debug!("AST: {:#?}", exprs);
-
- let mut evaluator = Evaluator::new();
-
- for expr in exprs {
- match evaluator.eval(&expr) {
- Ok(Value::Unit) => {},
- Ok(value) => println!("{:?}", value),
- Err(err) => log::error!("Evaluation error: {}", err),
- }
- }
- }
+ let exprs = match parser.parse() {
+ Ok(e) => e,
Err(errors) => {
- for error in errors {
- log::error!("Error: {}", error);
+ for err in errors {
+ eprintln!("Error: {}", err);
}
+ return;
+ }
+ };
+
+ let mut evaluator = Evaluator::new();
+ for expr in exprs {
+ match evaluator.eval(&expr) {
+ Ok(Value::Unit) => {}
+ Ok(value) => println!("{:?}", value),
+ Err(err) => eprintln!("Evaluation error: {}", err),
}
}
}