import OctiveLean.Eval import OctiveLean.Builtins import OctiveLean.PlotData import OctiveLean.PlotBuiltins import OctiveLean.PlotWidget import ProofWidgets.Component.HtmlDisplay import Lean /-! # OctiveLean Syntax DSL Octave embedded as a Lean 4 syntax category. The LSP recognizes every keyword and operator inside an `octave! { ... }` block — giving real syntax highlighting, hover, and completion. ## Usage ``` octave! { x = 42; for k = 1:5 x = x + k; endfor disp(x) } ``` ## Departures from standard Octave - Outer block: `octave! { ... }` (curly braces avoid collisions with Lean's `end`) - `endif` / `endfor` / `endwhile` / `endswitch` / `end_try_catch` / `endfunction` to close each control structure (these are real Octave keywords too) - Strings: `"..."` (Lean string literals — `'...'` is not supported) - Comments: `--` (Lean style — `%` is the modulo operator token) - Matrix rows are separated by `;`, columns by `,`: `[1.0, 2.0; 3.0, 4.0]` -/ namespace OctiveLean.DSL open Lean open OctiveLean /-- Convert a `String` to a `TSyntax `term` whose representation is a string literal. -/ private def quoteStr (s : String) : TSyntax `term := ⟨Lean.Syntax.mkStrLit s⟩ -- ───────────────────────────────────────────────────────────────── -- Syntax categories -- ───────────────────────────────────────────────────────────────── declare_syntax_cat octExpr declare_syntax_cat octStmt declare_syntax_cat octRow declare_syntax_cat octMatBody -- ───────────────────────────────────────────────────────────────── -- EXPRESSIONS -- ───────────────────────────────────────────────────────────────── -- Literals (true/false handled as identifiers in convExpr) -- Atomic forms must be at :max so they can be left-args of postfix rules. syntax:max num : octExpr syntax:max scientific : octExpr syntax:max str : octExpr -- Identifier syntax:max ident : octExpr -- Grouped syntax:max "(" octExpr ")" : octExpr -- Matrix literal: rows separated by ';', columns by ',' syntax octExpr,+ : octRow syntax octRow : octMatBody syntax octRow ";" octMatBody : octMatBody syntax:max "[" octMatBody "]" : octExpr syntax:max "[" "]" : octExpr -- empty matrix -- Function handles syntax:max "@" ident : octExpr syntax:max "@(" ident,* ")" octExpr : octExpr -- Unary syntax:75 "-" octExpr:75 : octExpr syntax:75 "!" octExpr:75 : octExpr -- Power (right-associative) syntax:74 octExpr:75 " ^ " octExpr:74 : octExpr syntax:74 octExpr:75 " .^ " octExpr:74 : octExpr -- Multiplication / division (left-associative) syntax:70 octExpr:70 " * " octExpr:71 : octExpr syntax:70 octExpr:70 " / " octExpr:71 : octExpr syntax:70 octExpr:70 " .* " octExpr:71 : octExpr syntax:70 octExpr:70 " ./ " octExpr:71 : octExpr -- Addition / subtraction syntax:65 octExpr:65 " + " octExpr:66 : octExpr syntax:65 octExpr:65 " - " octExpr:66 : octExpr -- Range a:b syntax:60 octExpr:61 " : " octExpr:61 : octExpr -- Comparison syntax:50 octExpr:51 " == " octExpr:51 : octExpr syntax:50 octExpr:51 " != " octExpr:51 : octExpr syntax:50 octExpr:51 " <= " octExpr:51 : octExpr syntax:50 octExpr:51 " >= " octExpr:51 : octExpr syntax:50 octExpr:51 " < " octExpr:51 : octExpr syntax:50 octExpr:51 " > " octExpr:51 : octExpr -- Logical syntax:40 octExpr:40 " && " octExpr:41 : octExpr syntax:40 octExpr:40 " || " octExpr:41 : octExpr syntax:40 octExpr:40 " & " octExpr:41 : octExpr syntax:40 octExpr:40 " | " octExpr:41 : octExpr -- Function call / matrix index syntax:max octExpr:max "(" octExpr,* ")" : octExpr -- ───────────────────────────────────────────────────────────────── -- STATEMENTS -- ───────────────────────────────────────────────────────────────── -- Expression statement syntax octExpr : octStmt syntax octExpr ";" : octStmt -- Assignment syntax ident " = " octExpr : octStmt syntax ident " = " octExpr ";" : octStmt -- Multi-assignment syntax "[" ident,+ "]" " = " octExpr : octStmt syntax "[" ident,+ "]" " = " octExpr ";" : octStmt -- IF / ELSEIF / ELSE / ENDIF syntax "if" octExpr octStmt* ("elseif" octExpr octStmt*)* ("else" octStmt*)? "endif" : octStmt -- FOR / ENDFOR syntax "for" ident " = " octExpr octStmt* "endfor" : octStmt -- WHILE / ENDWHILE syntax "while" octExpr octStmt* "endwhile" : octStmt -- SWITCH / CASE / OTHERWISE / ENDSWITCH syntax "switch" octExpr ("case" octExpr octStmt*)* ("otherwise" octStmt*)? "endswitch" : octStmt -- TRY / CATCH / END_TRY_CATCH syntax "try" octStmt* ("catch" ident octStmt*)? "end_try_catch" : octStmt -- Control flow syntax "return" : octStmt syntax "break" : octStmt syntax "continue" : octStmt -- Scope syntax "global" ident+ : octStmt syntax "clear" ident+ : octStmt -- Function definition syntax "function" ident " = " ident "(" ident,* ")" octStmt* "endfunction" : octStmt syntax "function" "[" ident,+ "]" " = " ident "(" ident,* ")" octStmt* "endfunction" : octStmt syntax "function" ident "(" ident,* ")" octStmt* "endfunction" : octStmt -- ───────────────────────────────────────────────────────────────── -- Macro conversion: octExpr → Term (of type OctiveLean.Expr) -- ───────────────────────────────────────────────────────────────── private partial def convExpr (e : Syntax) : MacroM (TSyntax `term) := do match e with -- Literals | `(octExpr| $n:num) => `(Expr.lit (.float ($n : Float))) | `(octExpr| $f:scientific) => `(Expr.lit (.float ($f : Float))) | `(octExpr| $s:str) => `(Expr.lit (.str $s)) -- Identifier (with special handling for `true`/`false`) | `(octExpr| $id:ident) => match id.getId.toString with | "true" => `(Expr.lit (.bool true)) | "false" => `(Expr.lit (.bool false)) | name => `(Expr.ident $(Lean.quote name)) -- Grouped | `(octExpr| ($x)) => convExpr x -- Unary | `(octExpr| - $x) => do `(Expr.unop .neg $(← convExpr x)) | `(octExpr| ! $x) => do `(Expr.unop .lnot $(← convExpr x)) -- Power | `(octExpr| $a ^ $b) => do `(Expr.binop .pow $(← convExpr a) $(← convExpr b)) | `(octExpr| $a .^ $b) => do `(Expr.binop .epow $(← convExpr a) $(← convExpr b)) -- Mul/Div | `(octExpr| $a * $b) => do `(Expr.binop .mul $(← convExpr a) $(← convExpr b)) | `(octExpr| $a / $b) => do `(Expr.binop .div $(← convExpr a) $(← convExpr b)) | `(octExpr| $a .* $b) => do `(Expr.binop .emul $(← convExpr a) $(← convExpr b)) | `(octExpr| $a ./ $b) => do `(Expr.binop .ediv $(← convExpr a) $(← convExpr b)) -- Add/Sub | `(octExpr| $a + $b) => do `(Expr.binop .add $(← convExpr a) $(← convExpr b)) | `(octExpr| $a - $b) => do `(Expr.binop .sub $(← convExpr a) $(← convExpr b)) -- Range (a:b — step defaults to 1; nested a:s:b parses as (a:s):b) | `(octExpr| $lo : $hi) => do match lo with | `(octExpr| $a : $step) => `(Expr.range $(← convExpr a) (some $(← convExpr step)) $(← convExpr hi)) | _ => `(Expr.range $(← convExpr lo) none $(← convExpr hi)) -- Comparison | `(octExpr| $a == $b) => do `(Expr.binop .eq $(← convExpr a) $(← convExpr b)) | `(octExpr| $a != $b) => do `(Expr.binop .ne $(← convExpr a) $(← convExpr b)) | `(octExpr| $a <= $b) => do `(Expr.binop .le $(← convExpr a) $(← convExpr b)) | `(octExpr| $a >= $b) => do `(Expr.binop .ge $(← convExpr a) $(← convExpr b)) | `(octExpr| $a < $b) => do `(Expr.binop .lt $(← convExpr a) $(← convExpr b)) | `(octExpr| $a > $b) => do `(Expr.binop .gt $(← convExpr a) $(← convExpr b)) -- Logical | `(octExpr| $a && $b) => do `(Expr.binop .land $(← convExpr a) $(← convExpr b)) | `(octExpr| $a || $b) => do `(Expr.binop .lor $(← convExpr a) $(← convExpr b)) | `(octExpr| $a & $b) => do `(Expr.binop .band $(← convExpr a) $(← convExpr b)) | `(octExpr| $a | $b) => do `(Expr.binop .bor $(← convExpr a) $(← convExpr b)) -- Function call / matrix index | `(octExpr| $f:octExpr ( $args:octExpr,* )) => do let fT ← convExpr f let aTs ← args.getElems.mapM (fun a => do `(Arg.pos $(← convExpr a))) `(Expr.index $fT #[$aTs,*]) -- Function handles | `(octExpr| @ $id:ident) => `(Expr.fnHandle $(Lean.quote id.getId.toString)) | `(octExpr| @( $params:ident,* ) $body:octExpr) => do let pNames := params.getElems.map (fun p => quoteStr p.getId.toString) `(Expr.anon #[$pNames,*] $(← convExpr body)) -- Matrix literal: empty | `(octExpr| [ ]) => `(Expr.matrix #[]) -- Matrix literal: with body (one or more rows) | `(octExpr| [ $body:octMatBody ]) => do let rowTerms ← collectRows body `(Expr.matrix #[$rowTerms,*]) | _ => Macro.throwErrorAt e "unsupported expression syntax" where convRow (row : Syntax) : MacroM (TSyntax `term) := do match row with | `(octRow| $cols:octExpr,*) => do let colTerms ← cols.getElems.mapM convExpr `(#[$colTerms,*]) | _ => Macro.throwErrorAt row "bad matrix row" collectRows (body : Syntax) : MacroM (Array (TSyntax `term)) := do match body with | `(octMatBody| $r:octRow) => do return #[← convRow r] | `(octMatBody| $r:octRow ; $rest:octMatBody) => do let rt ← convRow r let restRows ← collectRows rest return #[rt] ++ restRows | _ => Macro.throwErrorAt body "bad matrix body" -- ───────────────────────────────────────────────────────────────── -- Macro conversion: octStmt → Term (of type OctiveLean.Stmt) -- ───────────────────────────────────────────────────────────────── private partial def convStmt (s : Syntax) : MacroM (TSyntax `term) := do match s with -- Expression statements | `(octStmt| $e:octExpr ;) => do `(Stmt.exprS $(← convExpr e) true) | `(octStmt| $e:octExpr) => do `(Stmt.exprS $(← convExpr e) false) -- Assignments | `(octStmt| $x:ident = $e:octExpr ;) => do `(Stmt.assign #[$(Lean.quote x.getId.toString)] $(← convExpr e) true) | `(octStmt| $x:ident = $e:octExpr) => do `(Stmt.assign #[$(Lean.quote x.getId.toString)] $(← convExpr e) false) -- Multi-assignment | `(octStmt| [ $xs:ident,* ] = $e:octExpr ;) => do let names := xs.getElems.map (fun x => quoteStr x.getId.toString) `(Stmt.assign #[$names,*] $(← convExpr e) true) | `(octStmt| [ $xs:ident,* ] = $e:octExpr) => do let names := xs.getElems.map (fun x => quoteStr x.getId.toString) `(Stmt.assign #[$names,*] $(← convExpr e) false) -- IF | `(octStmt| if $cond:octExpr $thenB:octStmt* $[elseif $eiconds:octExpr $eibodies:octStmt*]* $[else $elseB:octStmt*]? endif) => do let condT ← convExpr cond let thenT ← thenB.mapM convStmt let eiTs ← (Array.zip eiconds eibodies).mapM (fun (c, body) => do let ct ← convExpr c let bt ← body.mapM convStmt `(($ct, #[$bt,*]))) let elseT ← match elseB with | none => `((none : Option (Array Stmt))) | some b => do let bt ← b.mapM convStmt; `(some #[$bt,*]) `(Stmt.ifS $condT #[$thenT,*] #[$eiTs,*] $elseT) -- FOR | `(octStmt| for $k:ident = $range:octExpr $body:octStmt* endfor) => do `(Stmt.forS $(Lean.quote k.getId.toString) $(← convExpr range) #[$(← body.mapM convStmt),*]) -- WHILE | `(octStmt| while $cond:octExpr $body:octStmt* endwhile) => do `(Stmt.whileS $(← convExpr cond) #[$(← body.mapM convStmt),*]) -- SWITCH | `(octStmt| switch $val:octExpr $[case $cvs:octExpr $cbs:octStmt*]* $[otherwise $ob:octStmt*]? endswitch) => do let valT ← convExpr val let brs ← (Array.zip cvs cbs).mapM (fun (cv, cb) => do let cvt ← convExpr cv let cbt ← cb.mapM convStmt `(($cvt, #[$cbt,*]))) let otT ← match ob with | none => `((none : Option (Array Stmt))) | some b => do let bt ← b.mapM convStmt; `(some #[$bt,*]) `(Stmt.switchS $valT #[$brs,*] $otT) -- TRY / CATCH | `(octStmt| try $tryB:octStmt* $[catch $evar:ident $catchB:octStmt*]? end_try_catch) => do let tryT ← tryB.mapM convStmt let catchT ← match evar, catchB with | some ev, some cb => do let cbt ← cb.mapM convStmt `(some ($(Lean.quote ev.getId.toString), #[$cbt,*])) | _, _ => `((none : Option (String × Array Stmt))) `(Stmt.tryS #[$tryT,*] $catchT) -- Control flow | `(octStmt| return) => `(Stmt.returnS) | `(octStmt| break) => `(Stmt.breakS) | `(octStmt| continue) => `(Stmt.continueS) -- Scope | `(octStmt| global $ids*) => do let names := ids.map (fun i => quoteStr i.getId.toString) `(Stmt.globalS #[$names,*]) | `(octStmt| clear $ids*) => do let names := ids.map (fun i => quoteStr i.getId.toString) `(Stmt.clearS #[$names,*]) -- Function defs | `(octStmt| function $ret:ident = $name:ident ( $params:ident,* ) $body:octStmt* endfunction) => do let pNames := params.getElems.map (fun p => quoteStr p.getId.toString) let bt ← body.mapM convStmt `(Stmt.funcDefS (FuncDef.mk $(quoteStr name.getId.toString) #[$pNames,*] #[$(quoteStr ret.getId.toString)] #[$bt,*])) | `(octStmt| function [ $rets:ident,* ] = $name:ident ( $params:ident,* ) $body:octStmt* endfunction) => do let pNames := params.getElems.map (fun p => quoteStr p.getId.toString) let rNames := rets.getElems.map (fun r => quoteStr r.getId.toString) let bt ← body.mapM convStmt `(Stmt.funcDefS (FuncDef.mk $(quoteStr name.getId.toString) #[$pNames,*] #[$rNames,*] #[$bt,*])) | `(octStmt| function $name:ident ( $params:ident,* ) $body:octStmt* endfunction) => do let pNames := params.getElems.map (fun p => quoteStr p.getId.toString) let bt ← body.mapM convStmt `(Stmt.funcDefS (FuncDef.mk $(quoteStr name.getId.toString) #[$pNames,*] #[] #[$bt,*])) | _ => Macro.throwErrorAt s "unsupported statement syntax" -- ───────────────────────────────────────────────────────────────── -- Source info helper: macro-generated syntax has canonical := false, -- which prevents `savePanelWidgetInfo` from binding the widget to a -- source position. Flip the flag. -- ───────────────────────────────────────────────────────────────── private def mkCanonicalInfo : Lean.SourceInfo → Lean.SourceInfo | .synthetic s e _ => .synthetic s e true | si => si private def mkCanonicalSyntax : Lean.Syntax → Lean.Syntax | .node i k a => .node (mkCanonicalInfo i) k a | .atom i v => .atom (mkCanonicalInfo i) v | .ident i r v p => .ident (mkCanonicalInfo i) r v p | s => s -- ───────────────────────────────────────────────────────────────── -- Top-level commands -- ───────────────────────────────────────────────────────────────── /-- `octave! { stmts }` — parse, type-check, and run the block. -/ syntax (name := octaveRun) "octave!" "{" octStmt* "}" : command macro_rules | `(command| octave! { $stmts:octStmt* }) => do let stmtTerms ← stmts.mapM convStmt let result : Lean.TSyntax `command ← `(#html (show IO ProofWidgets.Html from do let plotBuf ← IO.mkRef (#[] : Array OctiveLean.Figure) let env := OctiveLean.PlotBuiltins.register plotBuf (OctiveLean.registerAllBuiltins OctiveLean.Env.empty) match ← OctiveLean.runProgram #[$stmtTerms,*] env with | .ok _ => pure () | .error e => IO.eprintln s!"runtime error: {e}" let figs ← plotBuf.get return OctiveLean.PlotWidget.render figs)) return (⟨mkCanonicalSyntax result.raw⟩ : Lean.TSyntax `command) /-- `octave_program! name { stmts }` — bind the parsed AST to a Lean def. -/ syntax (name := octaveProg) "octave_program!" ident "{" octStmt* "}" : command macro_rules | `(command| octave_program! $name:ident { $stmts:octStmt* }) => do let stmtTerms ← stmts.mapM convStmt `(def $name : Array OctiveLean.Stmt := #[$stmtTerms,*]) end OctiveLean.DSL