Home
Insert
Update snippet 'Turtle procedures'
Title
Description
Minimal Logo implementation using FParsec with support for procedures.
Source code
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
Tags
turtle
dsl
fparsec
turtle
dsl
fparsec
Author
Link
Reference NuGet packages
If your snippet has external dependencies, enter the names of NuGet packages to reference, separated by a comma (
#r
directives are not required).
Update