module AST = type name = string type param = string type arg = Number of int | Arg of param type command = | Forward of arg | Left of arg | Right of arg | SetRandomPosition | Repeat of arg * command list | Call of name * arg list | Proc of name * param list * command list (*[omit:Windows Forms references]*) #if INTERACTIVE #r "System.Drawing.dll" #r "System.Windows.Forms.dll" #endif (*[/omit]*) module Interpreter = open AST open System open System.Drawing open System.Windows.Forms type Turtle = { X:float; Y:float; A:int } let execute commands = let procs = ref Map.empty let width, height = 640, 480 let form = new Form (Text="Turtle", Width=width, Height=height) let image = new Bitmap(width, height) let picture = new PictureBox(Dock=DockStyle.Fill, Image=image) do form.Controls.Add(picture) let turtle = { X=float width/2.0; Y=float height/2.0; A = -90 } use pen = new Pen(Color.Red) let rand = let r = Random() in fun n -> r.Next(n) |> float let drawLine (x1,y1) (x2,y2) = use graphics = Graphics.FromImage(image) graphics.DrawLine(pen,int x1,int y1,int x2, int y2) let rec perform env turtle = function | Forward arg -> let r = float turtle.A * Math.PI / 180.0 let n = getValue env arg let dx, dy = float n * cos r, float n * sin r let x, y = turtle.X, turtle.Y let x',y' = x + dx, y + dy drawLine (x,y) (x',y') { turtle with X = x'; Y = y' } | Left arg -> { turtle with A=turtle.A - getValue env arg } | Right arg -> { turtle with A=turtle.A + getValue env arg } | SetRandomPosition -> { turtle with X=rand width; Y=rand height } | Repeat(arg,commands) -> let n = getValue env arg let rec repeat turtle = function | 0 -> turtle | n -> repeat (performAll env turtle commands) (n-1) repeat turtle n | Proc(name, ps, commands) -> procs := Map.add name (ps,commands) !procs; turtle | Call(name,args) -> let ps, commands = (!procs).[name] if ps.Length <> args.Length then raise (ArgumentException("Parameter count mismatch")) let xs = List.zip ps args let env = xs |> List.fold (fun e (name,value) -> Map.add name value env) env commands |> performAll env turtle and performAll env turtle commands = commands |> List.fold (perform env) turtle and getValue env = function | Number n -> n | Arg name -> getValue env (Map.tryFind name env).Value performAll Map.empty turtle commands |> ignore form.ShowDialog() |> ignore (*[omit:FParsec references]*) #if INTERACTIVE #r @"..\..\packages\FParsec.1.0.1\lib\net40-client\FParsecCS.dll" #r @"..\..\packages\FParsec.1.0.1\lib\net40-client\FParsec.dll" #endif (*[/omit]*) module Parser = open AST open FParsec let procs = ref [] let pidentifier = let isIdentifierFirstChar c = isLetter c || c = '-' let isIdentifierChar c = isLetter c || isDigit c || c = '-' many1Satisfy2L isIdentifierFirstChar isIdentifierChar "identifier" let pparam = pstring ":" >>. pidentifier let pnumber = pfloat |>> fun n -> Number(int n) let parg = pnumber <|> (pparam |>> fun a -> Arg(a)) let pforward = (pstring "forward" <|> pstring "fd") >>. spaces1 >>. parg |>> fun arg -> Forward(arg) let pleft = (pstring "left" <|> pstring "lt") >>. spaces1 >>. parg |>> fun arg -> Left(arg) let pright = (pstring "right" <|> pstring "rt") >>. spaces1 >>. parg |>> fun arg -> Right(arg) let prandom = pstring "set-random-position" |>> fun _ -> SetRandomPosition let prepeat, prepeatimpl = createParserForwardedToRef () let pcall, pcallimpl = createParserForwardedToRef () let pcommand = pforward <|> pleft <|> pright <|> prandom <|> prepeat <|> pcall let updateCalls () = pcallimpl := choice [ for (name,ps) in !procs -> pstring name >>. spaces >>. (many parg .>> spaces) |>> fun args -> Call(name,args) ] updateCalls() let block = between (pstring "[" .>> spaces) (pstring "]") (sepEndBy pcommand spaces1) prepeatimpl := pstring "repeat" >>. spaces1 >>. parg .>> spaces .>>. block |>> fun (arg,commands) -> Repeat(arg, commands) let pparams = many (pparam .>> spaces) let pheader = pstring "to" >>. spaces1 >>. pidentifier .>> spaces1 .>>. pparams let pbody = many (pcommand .>> spaces1) let pfooter = pstring "end" let pproc = pheader .>>. pbody .>> pfooter |>> fun ((name,ps),body) -> procs := (name,ps)::!procs; updateCalls() Proc(name, ps, body) let parser = spaces >>. (sepEndBy (pcommand <|> pproc) spaces1) let parse code = match run parser code with | Success(result,_,_) -> result | Failure(msg,_,_) -> failwith msg let code = " to square repeat 4 [forward 50 right 90] end to flower repeat 36 [right 10 square] end to garden :count repeat :count [set-random-position flower] end garden 25 " let program = Parser.parse code Interpreter.execute program