Skip to content

Commit c207941

Browse files
Claude/add academic proofs mg s2z (#27)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 37b4691 commit c207941

4 files changed

Lines changed: 317 additions & 13 deletions

File tree

src/ast/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,14 @@ pub struct MatchArm {
215215
/// Pattern for matching
216216
#[derive(Debug, Clone)]
217217
pub enum Pattern {
218+
/// Literal pattern: `42`, `"hello"`, `true`
218219
Literal(Literal),
220+
/// Identifier pattern (binds value): `x`
219221
Identifier(String),
222+
/// Wildcard pattern: `_`
220223
Wildcard,
224+
/// Constructor pattern: `Okay(x)`, `Oops(e)`
225+
Constructor(String, Option<Box<Pattern>>),
221226
}
222227

223228
/// Expression types
@@ -239,6 +244,14 @@ pub enum Expr {
239244
GratitudeLiteral(String),
240245
/// Array literal
241246
Array(Vec<Spanned<Expr>>),
247+
/// Index access: `arr[i]` or `str[i]`
248+
Index(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
249+
/// Result success: `Okay(expr)`
250+
Okay(Box<Spanned<Expr>>),
251+
/// Result error: `Oops(expr)`
252+
Oops(Box<Spanned<Expr>>),
253+
/// Unwrap result: `expr?` or `unwrap(expr)`
254+
Unwrap(Box<Spanned<Expr>>),
242255
}
243256

244257
/// Binary operators

src/interpreter/mod.rs

Lines changed: 230 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,8 @@ impl Interpreter {
294294
for arm in &decide.arms {
295295
if self.pattern_matches(&arm.pattern, &scrutinee) {
296296
self.env.push_scope();
297-
// Bind pattern variables
298-
if let Pattern::Identifier(name) = &arm.pattern {
299-
self.env.define(name.clone(), scrutinee.clone());
300-
}
297+
// Bind pattern variables (handles Identifier, Constructor, etc.)
298+
self.bind_pattern(&arm.pattern, &scrutinee);
301299
for stmt in &arm.body {
302300
if let ControlFlow::Return(v) = self.execute_statement(stmt)? {
303301
self.env.pop_scope();
@@ -353,6 +351,45 @@ impl Interpreter {
353351
let lit_value = self.literal_to_value(lit);
354352
value == &lit_value
355353
}
354+
Pattern::Constructor(name, inner_pattern) => match (name.as_str(), value) {
355+
("Okay", Value::Okay(inner_val)) => {
356+
if let Some(pat) = inner_pattern {
357+
self.pattern_matches(pat, inner_val)
358+
} else {
359+
true
360+
}
361+
}
362+
("Oops", Value::Oops(_)) => {
363+
// Oops pattern matches any Oops value
364+
// The inner pattern (if any) can bind the error message
365+
true
366+
}
367+
_ => false,
368+
},
369+
}
370+
}
371+
372+
fn bind_pattern(&mut self, pattern: &Pattern, value: &Value) {
373+
match pattern {
374+
Pattern::Identifier(name) => {
375+
self.env.define(name.clone(), value.clone());
376+
}
377+
Pattern::Constructor(name, inner_pattern) => {
378+
if let Some(pat) = inner_pattern {
379+
match (name.as_str(), value) {
380+
("Okay", Value::Okay(inner_val)) => {
381+
self.bind_pattern(pat, inner_val);
382+
}
383+
("Oops", Value::Oops(err_msg)) => {
384+
self.bind_pattern(pat, &Value::String(err_msg.clone()));
385+
}
386+
_ => {}
387+
}
388+
}
389+
}
390+
Pattern::Wildcard | Pattern::Literal(_) => {
391+
// No bindings for wildcards or literals
392+
}
356393
}
357394
}
358395

@@ -413,6 +450,57 @@ impl Interpreter {
413450
.collect::<Result<_>>()?;
414451
Ok(Value::Array(values))
415452
}
453+
Expr::Index(target, index) => {
454+
let target_val = self.evaluate(target)?;
455+
let index_val = self.evaluate(index)?;
456+
self.apply_index(target_val, index_val)
457+
}
458+
Expr::Okay(inner) => {
459+
let val = self.evaluate(inner)?;
460+
Ok(Value::Okay(Box::new(val)))
461+
}
462+
Expr::Oops(inner) => {
463+
let val = self.evaluate(inner)?;
464+
match val {
465+
Value::String(s) => Ok(Value::Oops(s)),
466+
other => Ok(Value::Oops(other.to_string())),
467+
}
468+
}
469+
Expr::Unwrap(inner) => {
470+
let val = self.evaluate(inner)?;
471+
match val {
472+
Value::Okay(v) => Ok(*v),
473+
Value::Oops(e) => Err(RuntimeError::Complaint(e)),
474+
other => Ok(other), // Non-result values pass through
475+
}
476+
}
477+
}
478+
}
479+
480+
fn apply_index(&self, target: Value, index: Value) -> Result<Value> {
481+
let idx = match index {
482+
Value::Int(n) => {
483+
if n < 0 {
484+
return Err(RuntimeError::IndexOutOfBounds(n as usize));
485+
}
486+
n as usize
487+
}
488+
_ => return Err(RuntimeError::TypeError("Index must be an integer".into())),
489+
};
490+
491+
match target {
492+
Value::Array(arr) => arr
493+
.get(idx)
494+
.cloned()
495+
.ok_or(RuntimeError::IndexOutOfBounds(idx)),
496+
Value::String(s) => s
497+
.chars()
498+
.nth(idx)
499+
.map(|c| Value::String(c.to_string()))
500+
.ok_or(RuntimeError::IndexOutOfBounds(idx)),
501+
_ => Err(RuntimeError::TypeError(
502+
"Cannot index this type".into(),
503+
)),
416504
}
417505
}
418506

@@ -469,6 +557,49 @@ impl Interpreter {
469557
_ => Err(RuntimeError::TypeError("Cannot convert to Int".into())),
470558
}
471559
}
560+
"isOkay" => {
561+
if args.len() != 1 {
562+
return Err(RuntimeError::ArityMismatch {
563+
expected: 1,
564+
got: args.len(),
565+
});
566+
}
567+
Ok(Some(Value::Bool(args[0].is_okay())))
568+
}
569+
"isOops" => {
570+
if args.len() != 1 {
571+
return Err(RuntimeError::ArityMismatch {
572+
expected: 1,
573+
got: args.len(),
574+
});
575+
}
576+
Ok(Some(Value::Bool(args[0].is_oops())))
577+
}
578+
"unwrapOr" => {
579+
if args.len() != 2 {
580+
return Err(RuntimeError::ArityMismatch {
581+
expected: 2,
582+
got: args.len(),
583+
});
584+
}
585+
match &args[0] {
586+
Value::Okay(v) => Ok(Some((**v).clone())),
587+
Value::Oops(_) => Ok(Some(args[1].clone())),
588+
other => Ok(Some(other.clone())),
589+
}
590+
}
591+
"getError" => {
592+
if args.len() != 1 {
593+
return Err(RuntimeError::ArityMismatch {
594+
expected: 1,
595+
got: args.len(),
596+
});
597+
}
598+
match &args[0] {
599+
Value::Oops(e) => Ok(Some(Value::String(e.clone()))),
600+
_ => Ok(Some(Value::Unit)),
601+
}
602+
}
472603
_ => Ok(None), // Not a builtin
473604
}
474605
}
@@ -678,4 +809,99 @@ mod tests {
678809
"#;
679810
assert!(run_program(source).is_ok());
680811
}
812+
813+
#[test]
814+
fn test_result_okay() {
815+
let source = r#"
816+
to main() {
817+
remember result = Okay(42);
818+
remember is_ok = isOkay(result);
819+
}
820+
"#;
821+
assert!(run_program(source).is_ok());
822+
}
823+
824+
#[test]
825+
fn test_result_oops() {
826+
let source = r#"
827+
to main() {
828+
remember result = Oops("Something went wrong");
829+
remember is_err = isOops(result);
830+
}
831+
"#;
832+
assert!(run_program(source).is_ok());
833+
}
834+
835+
#[test]
836+
fn test_unwrap_or() {
837+
let source = r#"
838+
to main() {
839+
remember ok_result = Okay(10);
840+
remember err_result = Oops("error");
841+
remember val1 = unwrapOr(ok_result, 0);
842+
remember val2 = unwrapOr(err_result, 0);
843+
}
844+
"#;
845+
assert!(run_program(source).is_ok());
846+
}
847+
848+
#[test]
849+
fn test_array_indexing() {
850+
let source = r#"
851+
to main() {
852+
remember arr = [1, 2, 3, 4, 5];
853+
remember first = arr[0];
854+
remember third = arr[2];
855+
}
856+
"#;
857+
assert!(run_program(source).is_ok());
858+
}
859+
860+
#[test]
861+
fn test_string_indexing() {
862+
let source = r#"
863+
to main() {
864+
remember str = "hello";
865+
remember first_char = str[0];
866+
}
867+
"#;
868+
assert!(run_program(source).is_ok());
869+
}
870+
871+
#[test]
872+
fn test_decide_with_result() {
873+
let source = r#"
874+
to process(val: Int) -> Result {
875+
when val > 0 {
876+
give back Okay(val * 2);
877+
} otherwise {
878+
give back Oops("Value must be positive");
879+
}
880+
}
881+
882+
to main() {
883+
remember result = process(5);
884+
decide based on result {
885+
Okay(x) -> {
886+
print(x);
887+
}
888+
Oops(e) -> {
889+
print(e);
890+
}
891+
}
892+
}
893+
"#;
894+
assert!(run_program(source).is_ok());
895+
}
896+
897+
#[test]
898+
fn test_chained_indexing() {
899+
let source = r#"
900+
to main() {
901+
remember matrix = [[1, 2], [3, 4]];
902+
remember val = matrix[0][1];
903+
}
904+
"#;
905+
assert!(run_program(source).is_ok());
906+
}
681907
}

src/interpreter/value.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ pub enum Value {
99
Bool(bool),
1010
Array(Vec<Value>),
1111
Unit,
12+
/// Result success: `Okay(value)`
13+
Okay(Box<Value>),
14+
/// Result error: `Oops(message)`
15+
Oops(String),
1216
}
1317

1418
impl Value {
@@ -21,6 +25,27 @@ impl Value {
2125
Value::String(s) => !s.is_empty(),
2226
Value::Array(a) => !a.is_empty(),
2327
Value::Unit => false,
28+
Value::Okay(_) => true,
29+
Value::Oops(_) => false,
30+
}
31+
}
32+
33+
/// Check if this is an Okay result
34+
pub fn is_okay(&self) -> bool {
35+
matches!(self, Value::Okay(_))
36+
}
37+
38+
/// Check if this is an Oops result
39+
pub fn is_oops(&self) -> bool {
40+
matches!(self, Value::Oops(_))
41+
}
42+
43+
/// Unwrap an Okay value, or return the error
44+
pub fn unwrap(self) -> Result<Value, String> {
45+
match self {
46+
Value::Okay(v) => Ok(*v),
47+
Value::Oops(e) => Err(e),
48+
other => Ok(other), // Non-result values pass through
2449
}
2550
}
2651
}
@@ -43,6 +68,8 @@ impl fmt::Display for Value {
4368
write!(f, "]")
4469
}
4570
Value::Unit => write!(f, "()"),
71+
Value::Okay(v) => write!(f, "Okay({})", v),
72+
Value::Oops(e) => write!(f, "Oops(\"{}\")", e),
4673
}
4774
}
4875
}

0 commit comments

Comments
 (0)