2 people like it.

Code Kata: Writing Markdown parser

Sample for Coding Kata

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
78: 
79: 
80: 
81: 
82: 
83: 
84: 
85: 
86: 
87: 
88: 
#r "nunit.framework.dll"
open System
open NUnit.Framework

// ----------------------------------------------------------------------------

let toString chars =
  System.String(chars |> Array.ofList)

// TODO: Read until the end of inline code
let rec parseInlineBody acc chars = 
  failwith "!"

// TODO: Match beginning and read until the end
let parseInline chars = 
  failwith "!"

// ----------------------------------------------------------------------------

// Definition of markdown span element
type MarkdownSpan =
  | Literal of string
  | InlineCode of string

// Parse spans of the input
let rec parseSpans acc chars = seq {
  // emit literal if we skipped some characters
  let emitLiteral() = seq {
    if acc <> [] then 
      yield acc |> List.rev |> toString |> Literal }

  // try parsing inline
  match parseInline chars, chars with
  | Some(body, chars), _ ->
      yield! emitLiteral ()
      // TODO: Produce single 'InlineCode' element
      // TODO: Continue recursively
  | _, c::chars ->
      // TODO: Add 'c' to unconsumed characters
      // and continue recursively
  | _, [] ->
      // Finished, emit the remaining unprocessed characters
      yield! emitLiteral () }

// ----------------------------------------------------------------------------

[<TestFixture>]
module Tests = 

  // parseInlineBody

  [<Test>]
  let ``End of inline is found`` () =
    let res = "aa`bb" |> List.ofSeq |> parseInlineBody []
    Assert.That((res = (['a'; 'a'], ['b'; 'b'])))

  [<Test>]
  let ``All input is consumed`` () =
    let res = "aa" |> List.ofSeq |> parseInlineBody []
    Assert.That((res = (['a'; 'a'], [])))

  // parseInline

  [<Test>]
  let ``Needs backtick`` () =
    let res = "aa" |> List.ofSeq |> parseInline
    Assert.That((res = None))

  [<Test>]
  let ``Finds inline code`` () =
    let res = "`aa`bb" |> List.ofSeq |> parseInline
    Assert.That((res = Some (['a'; 'a'], ['b'; 'b'])))

  // parseSpans

  [<Test>]
  let ``Parse two inline snippets`` () =
    let res = "`a` `c`" |> List.ofSeq |> parseSpans [] |> List.ofSeq
    Assert.That((res = [InlineCode "a"; Literal " "; InlineCode "c"]))

// ----------------------------------------------------------------------------

  do 
    ``End of inline is found``()
    ``All input is consumed``()
    ``Needs backtick`` ()
    ``Finds inline code`` ()
    ``Parse two inline snippets`` ()
namespace System
val toString : chars:char list -> String

Full name: Script.toString
val chars : char list
Multiple items
type String =
  new : value:char -> string + 7 overloads
  member Chars : int -> char
  member Clone : unit -> obj
  member CompareTo : value:obj -> int + 1 overload
  member Contains : value:string -> bool
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EndsWith : value:string -> bool + 2 overloads
  member Equals : obj:obj -> bool + 2 overloads
  member GetEnumerator : unit -> CharEnumerator
  member GetHashCode : unit -> int
  ...

Full name: System.String

--------------------
String(value: nativeptr<char>) : unit
String(value: nativeptr<sbyte>) : unit
String(value: char []) : unit
String(c: char, count: int) : unit
String(value: nativeptr<char>, startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
String(value: char [], startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : unit
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
val ofList : list:'T list -> 'T []

Full name: Microsoft.FSharp.Collections.Array.ofList
val parseInlineBody : acc:'a -> chars:'b -> 'c

Full name: Script.parseInlineBody
val acc : 'a
val chars : 'b
val failwith : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.failwith
val parseInline : chars:'a -> 'b

Full name: Script.parseInline
val chars : 'a
type MarkdownSpan =
  | Literal of string
  | InlineCode of string

Full name: Script.MarkdownSpan
Multiple items
union case MarkdownSpan.Literal: string -> MarkdownSpan

--------------------
type LiteralAttribute =
  inherit Attribute
  new : unit -> LiteralAttribute

Full name: Microsoft.FSharp.Core.LiteralAttribute

--------------------
new : unit -> LiteralAttribute
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
union case MarkdownSpan.InlineCode: string -> MarkdownSpan
val parseSpans : acc:char list -> chars:'a list -> seq<MarkdownSpan>

Full name: Script.parseSpans
val acc : char list
val chars : 'a list
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

--------------------
type seq<'T> = Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
val emitLiteral : (unit -> seq<MarkdownSpan>)
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val rev : list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.rev
union case Option.Some: Value: 'T -> Option<'T>
val body : obj
val chars : obj
val c : 'a
module Tests

from Script
val ( End of inline is found ) : unit -> 'a

Full name: Script.Tests.( End of inline is found )
val res : obj
val ofSeq : source:seq<'T> -> 'T list

Full name: Microsoft.FSharp.Collections.List.ofSeq
val ( All input is consumed ) : unit -> 'a

Full name: Script.Tests.( All input is consumed )
val ( Needs backtick ) : unit -> 'a

Full name: Script.Tests.( Needs backtick )
union case Option.None: Option<'T>
val ( Finds inline code ) : unit -> 'a

Full name: Script.Tests.( Finds inline code )
val ( Parse two inline snippets ) : unit -> 'a

Full name: Script.Tests.( Parse two inline snippets )
val res : MarkdownSpan list
Raw view New version

More information

Link:http://fssnip.net/dT
Posted:4 years ago
Author:Tomas Petricek
Tags: kata