open Combinator type Color = | Red | Green | Blue | Purple type Expr = | Line of Color | Repeat of int * Expr type Line = Color type Exprs = Expr list let CANVAS_HEIGHT = 400 let CANVAS_WIDTH = 400 let SVG_PREFIX = "\n" let SVG_SUFFIX = "\n" (* * ::= * | repeat * | * ::= line * ::= + * ::= 0 | 1 | 2 | … | 9 * ::= red | green | blue | purple *) let pad p = pbetween pws0 p pws0 let color = (pstr "red" |>> fun _ -> Red) <|> (pstr "green" |>> fun _ -> Green) <|> (pstr "blue" |>> fun _ -> Blue) <|> (pstr "purple" |>> fun _ -> Purple) let line = pleft (pad color) (pad (pstr "line")) |>> Line let n = pmany1 pdigit |>> stringify |>> int let repeat = pright (pad (pstr "repeat")) (pseq n line Repeat) let expr = pmany0 (line <|> repeat) let grammar = pleft expr peof let parse (i: string) : Exprs option = let input = prepare i match grammar input with | Success (ast, _) -> Some ast | Failure _ -> None let evalColor (c: Color) : string = match c with | Red -> "rgb(255,0,0)" | Green -> "rgb(0,255,0)" | Blue -> "rgb(0,0,255)" | Purple -> "rgb(128,8,165)" let rec evalExpr (e: Expr) : string = match e with | Line c -> // draw a random line let r = new System.Random() let start_x = r.Next CANVAS_WIDTH let start_y = r.Next CANVAS_HEIGHT let end_x = r.Next CANVAS_WIDTH let end_y = r.Next CANVAS_HEIGHT // get the color let rgb = evalColor c // generate SVG $"" | Repeat (n, x) -> let svg_list = [0..n-1] |> List.map (fun _ -> evalExpr x) System.String.Join("\n", svg_list) let rec eval (es: Exprs) : string = let ls_svgs = es |> List.map evalExpr let inner_svg = System.String.Join("\n", ls_svgs) SVG_PREFIX + inner_svg + SVG_SUFFIX let usage() = printfn "Usage: dotnet run " exit 1 [] let main args = if Array.length args <> 1 then printfn "ERROR: Must provide an input line program." usage() let filename = args[0] if not (System.IO.File.Exists filename) then printfn $"ERROR: Cannot find '{filename}'." usage() let input = System.IO.File.ReadAllText filename let ast_maybe = parse input match ast_maybe with | Some ast -> printfn "%s" (eval ast) | None -> printfn "Invalid linelang" 0