commit - a40058a5b5365b5e05a32eb8ba8ef34d2d3b51ab
commit + e415d90b662b2024a6f38196ded11f3881cbf636
blob - /dev/null
blob + fb580a3dd5681dd53cdf648d3238b0d3ff185147 (mode 644)
--- /dev/null
+++ src/compiler/mod.rs
+// vim: set tw=79 cc=80 ts=4 sw=4 sts=4 et :
+//
+// Copyright (c) 2025-2026 Murilo Ijanc' <murilo@ijanc.org>
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+pub mod opcode;
+
+use crate::error::OlangError;
+use crate::parser::ast::{BinOp, Expr, Program, Stmt, UnaryOp};
+use crate::span::Span;
+use crate::vm::value::Value;
+use opcode::{Chunk, OpCode};
+
+pub struct Compiler {
+ chunk: Chunk,
+}
+
+impl Compiler {
+ pub fn new() -> Self {
+ Self { chunk: Chunk::new() }
+ }
+
+ pub fn compile(
+ mut self, program: &Program,
+ ) -> Result<Chunk, OlangError> {
+ let main_fn = program.functions.iter()
+ .find(|f| f.name == "main")
+ .ok_or_else(|| OlangError::new(
+ "no main() function found", Span::new(0, 0),
+ ))?;
+
+ for stmt in &main_fn.body {
+ self.compile_stmt(stmt)?;
+ }
+
+ self.chunk.emit_op(OpCode::Return, 0);
+ Ok(self.chunk)
+ }
+
+ fn compile_stmt(&mut self, stmt: &Stmt) -> Result<(), OlangError> {
+ match stmt {
+ Stmt::Print { args, .. } => {
+ for arg in args {
+ self.compile_expr(arg)?;
+ }
+ self.chunk.emit_op(OpCode::Print, 0);
+ self.chunk.emit_byte(args.len() as u8, 0);
+ }
+ Stmt::ExprStmt { expr, .. } => {
+ self.compile_expr(expr)?;
+ self.chunk.emit_op(OpCode::Pop, 0);
+ }
+ _ => {
+ return Err(OlangError::new(
+ format!("unsupported statement: {:?}", stmt),
+ Span::new(0, 0),
+ ));
+ }
+ }
+ Ok(())
+ }
+
+ fn compile_expr(&mut self, expr: &Expr) -> Result<(), OlangError> {
+ match expr {
+ Expr::IntLit(v, _) => {
+ self.chunk.emit_constant(Value::Int(*v), 0);
+ }
+ Expr::FloatLit(v, _) => {
+ self.chunk.emit_constant(Value::Float(*v), 0);
+ }
+ Expr::StrLit(s, _) => {
+ self.chunk.emit_constant(Value::Str(s.clone()), 0);
+ }
+ Expr::BoolLit(v, _) => {
+ self.chunk.emit_constant(Value::Bool(*v), 0);
+ }
+ Expr::Unary { op, expr, .. } => {
+ self.compile_expr(expr)?;
+ match op {
+ UnaryOp::Negate => self.chunk.emit_op(OpCode::Negate, 0),
+ UnaryOp::Not => {
+ return Err(OlangError::new(
+ "not operator not yet supported", Span::new(0, 0),
+ ));
+ }
+ }
+ }
+ Expr::Binary { op, left, right, .. } => {
+ self.compile_expr(left)?;
+ self.compile_expr(right)?;
+ let opcode = match op {
+ BinOp::Add => OpCode::Add,
+ BinOp::Sub => OpCode::Sub,
+ BinOp::Mul => OpCode::Mul,
+ BinOp::Div => OpCode::Div,
+ BinOp::Mod => OpCode::Mod,
+ _ => {
+ return Err(OlangError::new(
+ format!("unsupported binary op: {:?}", op),
+ Span::new(0, 0),
+ ));
+ }
+ };
+ self.chunk.emit_op(opcode, 0);
+ }
+ _ => {
+ return Err(OlangError::new(
+ format!("unsupported expression: {:?}", expr),
+ Span::new(0, 0),
+ ));
+ }
+ }
+ Ok(())
+ }
+}
blob - /dev/null
blob + 9c9cbdf349de944ccdf23437fd67f1d7b8e00860 (mode 644)
--- /dev/null
+++ src/compiler/opcode.rs
+// vim: set tw=79 cc=80 ts=4 sw=4 sts=4 et :
+//
+// Copyright (c) 2025-2026 Murilo Ijanc' <murilo@ijanc.org>
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+use crate::vm::value::Value;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[repr(u8)]
+pub enum OpCode {
+ Constant,
+ Pop,
+ Add,
+ Sub,
+ Mul,
+ Div,
+ Mod,
+ Negate,
+ Print,
+ Return,
+}
+
+impl OpCode {
+ pub fn from_byte(byte: u8) -> Option<OpCode> {
+ match byte {
+ 0 => Some(OpCode::Constant),
+ 1 => Some(OpCode::Pop),
+ 2 => Some(OpCode::Add),
+ 3 => Some(OpCode::Sub),
+ 4 => Some(OpCode::Mul),
+ 5 => Some(OpCode::Div),
+ 6 => Some(OpCode::Mod),
+ 7 => Some(OpCode::Negate),
+ 8 => Some(OpCode::Print),
+ 9 => Some(OpCode::Return),
+ _ => None,
+ }
+ }
+}
+
+pub struct Chunk {
+ pub code: Vec<u8>,
+ pub constants: Vec<Value>,
+ pub lines: Vec<usize>,
+}
+
+impl Chunk {
+ pub fn new() -> Self {
+ Self {
+ code: Vec::new(),
+ constants: Vec::new(),
+ lines: Vec::new(),
+ }
+ }
+
+ pub fn emit_byte(&mut self, byte: u8, line: usize) {
+ self.code.push(byte);
+ self.lines.push(line);
+ }
+
+ pub fn emit_op(&mut self, op: OpCode, line: usize) {
+ self.emit_byte(op as u8, line);
+ }
+
+ pub fn add_constant(&mut self, value: Value) -> u8 {
+ self.constants.push(value);
+ (self.constants.len() - 1) as u8
+ }
+
+ pub fn emit_constant(&mut self, value: Value, line: usize) {
+ let index = self.add_constant(value);
+ self.emit_op(OpCode::Constant, line);
+ self.emit_byte(index, line);
+ }
+
+ pub fn disassemble(&self, name: &str) {
+ println!("== {name} ==");
+ let mut offset = 0;
+ while offset < self.code.len() {
+ offset = self.disassemble_instruction(offset);
+ }
+ }
+
+ fn disassemble_instruction(&self, offset: usize) -> usize {
+ print!("{:04} ", offset);
+ let byte = self.code[offset];
+ let Some(op) = OpCode::from_byte(byte) else {
+ println!("unknown opcode {byte}");
+ return offset + 1;
+ };
+ match op {
+ OpCode::Constant => {
+ let index = self.code[offset + 1] as usize;
+ let value = &self.constants[index];
+ println!("{:<12} {:4} ({})", "Constant", index, value);
+ offset + 2
+ }
+ OpCode::Print => {
+ let count = self.code[offset + 1] as usize;
+ println!("{:<12} {:4}", "Print", count);
+ offset + 2
+ }
+ _ => {
+ println!("{:?}", op);
+ offset + 1
+ }
+ }
+ }
+}
blob - 0e3ae3e1108786e6afd353e596e2f0870e775006
blob + fa4dce2b2edbef541e42a742ef2aeeaff7999c20
--- src/main.rs
+++ src/main.rs
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
+mod compiler;
mod error;
mod lexer;
mod parser;
mod span;
+mod vm;
use std::env;
use std::fs;
if args.len() < 3 {
eprintln!("usage: ol <command> <file>");
- eprintln!("commands: tokenize, parse");
+ eprintln!("commands: run, tokenize, parse, compile");
process::exit(1);
}
let source = match fs::read_to_string(filename) {
Ok(s) => s,
Err(e) => {
- eprintln!(
- "error reading '{filename}': {e}"
- );
+ eprintln!("error reading '{filename}': {e}");
process::exit(1);
}
};
match command.as_str() {
+ "run" => cmd_run(&source),
"tokenize" => cmd_tokenize(&source),
"parse" => cmd_parse(&source),
+ "compile" => cmd_compile(&source),
_ => {
eprintln!("unknown command: {command}");
process::exit(1);
}
}
+fn cmd_run(source: &str) {
+ let mut lexer = lexer::Lexer::new(source);
+ let tokens = match lexer.tokenize() {
+ Ok(t) => t,
+ Err(e) => { eprintln!("{e}"); process::exit(1); }
+ };
+ let mut parser = parser::Parser::new(tokens);
+ let program = match parser.parse_program() {
+ Ok(p) => p,
+ Err(e) => { eprintln!("{e}"); process::exit(1); }
+ };
+ let comp = compiler::Compiler::new();
+ let chunk = match comp.compile(&program) {
+ Ok(c) => c,
+ Err(e) => { eprintln!("{e}"); process::exit(1); }
+ };
+ let mut machine = vm::VM::new(chunk);
+ if let Err(e) = machine.run() {
+ eprintln!("runtime error: {e}");
+ process::exit(1);
+ }
+}
+
+fn cmd_compile(source: &str) {
+ let mut lexer = lexer::Lexer::new(source);
+ let tokens = match lexer.tokenize() {
+ Ok(t) => t,
+ Err(e) => { eprintln!("{e}"); process::exit(1); }
+ };
+ let mut parser = parser::Parser::new(tokens);
+ let program = match parser.parse_program() {
+ Ok(p) => p,
+ Err(e) => { eprintln!("{e}"); process::exit(1); }
+ };
+ let comp = compiler::Compiler::new();
+ match comp.compile(&program) {
+ Ok(chunk) => chunk.disassemble("main"),
+ Err(e) => { eprintln!("{e}"); process::exit(1); }
+ }
+}
+
fn cmd_parse(source: &str) {
let mut lexer = lexer::Lexer::new(source);
let tokens = match lexer.tokenize() {
Ok(t) => t,
- Err(e) => {
- eprintln!("{e}");
- process::exit(1);
- }
+ Err(e) => { eprintln!("{e}"); process::exit(1); }
};
-
let mut parser = parser::Parser::new(tokens);
match parser.parse_program() {
Ok(program) => println!("{:#?}", program),
- Err(e) => {
- eprintln!("{e}");
- process::exit(1);
- }
+ Err(e) => { eprintln!("{e}"); process::exit(1); }
}
}
fn cmd_tokenize(source: &str) {
let mut lexer = lexer::Lexer::new(source);
-
match lexer.tokenize() {
Ok(tokens) => {
let parts: Vec<String> = tokens
.collect();
println!("{}", parts.join(" "));
}
- Err(e) => {
- eprintln!("{e}");
- process::exit(1);
- }
+ Err(e) => { eprintln!("{e}"); process::exit(1); }
}
}
blob - /dev/null
blob + 6e43e1f2d1aa8021436c58f5f3813ae58beb6812 (mode 644)
--- /dev/null
+++ src/vm/mod.rs
+// vim: set tw=79 cc=80 ts=4 sw=4 sts=4 et :
+//
+// Copyright (c) 2025-2026 Murilo Ijanc' <murilo@ijanc.org>
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+pub mod value;
+
+use crate::compiler::opcode::{Chunk, OpCode};
+use value::Value;
+
+pub struct VM {
+ chunk: Chunk,
+ ip: usize,
+ stack: Vec<Value>,
+}
+
+impl VM {
+ pub fn new(chunk: Chunk) -> Self {
+ Self {
+ chunk,
+ ip: 0,
+ stack: Vec::new(),
+ }
+ }
+
+ fn read_byte(&mut self) -> u8 {
+ let byte = self.chunk.code[self.ip];
+ self.ip += 1;
+ byte
+ }
+
+ fn push(&mut self, value: Value) {
+ self.stack.push(value);
+ }
+
+ fn pop(&mut self) -> Value {
+ self.stack.pop().expect("stack underflow")
+ }
+
+ pub fn run(&mut self) -> Result<(), String> {
+ loop {
+ let byte = self.read_byte();
+ let Some(op) = OpCode::from_byte(byte)
+ else {
+ return Err(format!(
+ "unknown opcode: {byte}"
+ ));
+ };
+
+ match op {
+ OpCode::Constant => {
+ let index =
+ self.read_byte() as usize;
+ let value =
+ self.chunk.constants[index]
+ .clone();
+ self.push(value);
+ }
+ OpCode::Pop => {
+ self.pop();
+ }
+ OpCode::Add => {
+ let b = self.pop();
+ let a = self.pop();
+ let result = match (&a, &b) {
+ (Value::Int(a), Value::Int(b)) =>
+ Value::Int(a + b),
+ (Value::Float(a), Value::Float(b)) =>
+ Value::Float(a + b),
+ (Value::Str(a), Value::Str(b)) =>
+ Value::Str(format!("{a}{b}")),
+ _ => return Err(format!(
+ "cannot add {:?} and {:?}", a, b
+ )),
+ };
+ self.push(result);
+ }
+ OpCode::Sub => {
+ let b = self.pop();
+ let a = self.pop();
+ let result = match (&a, &b) {
+ (Value::Int(a), Value::Int(b)) =>
+ Value::Int(a - b),
+ (Value::Float(a), Value::Float(b)) =>
+ Value::Float(a - b),
+ _ => return Err(format!(
+ "cannot subtract {:?} and {:?}", a, b
+ )),
+ };
+ self.push(result);
+ }
+ OpCode::Mul => {
+ let b = self.pop();
+ let a = self.pop();
+ let result = match (&a, &b) {
+ (Value::Int(a), Value::Int(b)) =>
+ Value::Int(a * b),
+ (Value::Float(a), Value::Float(b)) =>
+ Value::Float(a * b),
+ _ => return Err(format!(
+ "cannot multiply {:?} and {:?}", a, b
+ )),
+ };
+ self.push(result);
+ }
+ OpCode::Div => {
+ let b = self.pop();
+ let a = self.pop();
+ let result = match (&a, &b) {
+ (Value::Int(a), Value::Int(b)) => {
+ if *b == 0 { return Err("division by zero".to_string()); }
+ Value::Int(a / b)
+ }
+ (Value::Float(a), Value::Float(b)) =>
+ Value::Float(a / b),
+ _ => return Err(format!(
+ "cannot divide {:?} and {:?}", a, b
+ )),
+ };
+ self.push(result);
+ }
+ OpCode::Mod => {
+ let b = self.pop();
+ let a = self.pop();
+ let result = match (&a, &b) {
+ (Value::Int(a), Value::Int(b)) => {
+ if *b == 0 { return Err("modulo by zero".to_string()); }
+ Value::Int(a % b)
+ }
+ _ => return Err(format!(
+ "cannot modulo {:?} and {:?}", a, b
+ )),
+ };
+ self.push(result);
+ }
+ OpCode::Negate => {
+ let v = self.pop();
+ let result = match v {
+ Value::Int(n) => Value::Int(-n),
+ Value::Float(n) => Value::Float(-n),
+ _ => return Err(format!("cannot negate {:?}", v)),
+ };
+ self.push(result);
+ }
+ OpCode::Print => {
+ let count = self.read_byte() as usize;
+ let start = self.stack.len() - count;
+ let args: Vec<Value> =
+ self.stack.drain(start..).collect();
+ let output: Vec<String> =
+ args.iter().map(|v| format!("{v}")).collect();
+ println!("{}", output.join(" "));
+ }
+ OpCode::Return => {
+ return Ok(());
+ }
+ }
+ }
+ }
+}
blob - /dev/null
blob + b4a9c703ba469c408293df4caec68392e45be323 (mode 644)
--- /dev/null
+++ src/vm/value.rs
+// vim: set tw=79 cc=80 ts=4 sw=4 sts=4 et :
+//
+// Copyright (c) 2025-2026 Murilo Ijanc' <murilo@ijanc.org>
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+use std::fmt;
+
+#[derive(Debug, Clone)]
+pub enum Value {
+ Int(i64),
+ Float(f64),
+ Str(String),
+ Bool(bool),
+}
+
+impl fmt::Display for Value {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Value::Int(v) => write!(f, "{v}"),
+ Value::Float(v) => write!(f, "{v}"),
+ Value::Str(v) => write!(f, "{v}"),
+ Value::Bool(v) => write!(f, "{v}"),
+ }
+ }
+}