feat(match): add decision-tree compiler for pattern matching
This commit is contained in:
1 parent
14fdc32383
commit
b6937f1b30
1 file changed
+77
@@ -272,3 +272,80 @@ let checkMatch (patterns: Pattern list) : unit =
|
|||||||
| Some(witness :: _) ->
|
| Some(witness :: _) ->
|
||||||
raise (MatchError(sprintf "non-exhaustive match: no clause covers %s" (renderCPat witness)))
|
raise (MatchError(sprintf "non-exhaustive match: no clause covers %s" (renderCPat witness)))
|
||||||
| _ -> ()
|
| _ -> ()
|
||||||
|
|
||||||
|
let private testOfTag (tag: string) : SwitchTest =
|
||||||
|
if tag.StartsWith "#int:" then SInt(int (tag.Substring 5))
|
||||||
|
elif tag.StartsWith "#str:" then SStr(tag.Substring 5)
|
||||||
|
elif tag = "#bool:true" then SBool true
|
||||||
|
elif tag = "#bool:false" then SBool false
|
||||||
|
elif tag = "#unit" then SUnit
|
||||||
|
elif tag = "#nil" then SNil
|
||||||
|
elif tag = "#cons" then SCons
|
||||||
|
elif tag.StartsWith "#tuple:" then STuple
|
||||||
|
elif tag.StartsWith "#record:" then SRecord
|
||||||
|
else SCtor tag
|
||||||
|
|
||||||
|
let rec private bindingsAt (prefix: Path) (p: Pattern) : (string * Path) list =
|
||||||
|
match p with
|
||||||
|
| PWildcard | PInt _ | PBool _ | PString _ | PUnit | PNil -> []
|
||||||
|
| PVar x -> [ x, prefix ]
|
||||||
|
| PTuple ps -> ps |> List.mapi (fun i sp -> bindingsAt (prefix @ [ i ]) sp) |> List.concat
|
||||||
|
| PCons(h, t) -> bindingsAt (prefix @ [ 0 ]) h @ bindingsAt (prefix @ [ 1 ]) t
|
||||||
|
| PConstruct(_, ps) -> ps |> List.mapi (fun i sp -> bindingsAt (prefix @ [ i ]) sp) |> List.concat
|
||||||
|
| PRecord fields ->
|
||||||
|
recordOrderedPatterns fields
|
||||||
|
|> List.mapi (fun i sp -> bindingsAt (prefix @ [ i ]) sp)
|
||||||
|
|> List.concat
|
||||||
|
|
||||||
|
let private replaceAt (index: int) (replacement: 'a list) (source: 'a list) : 'a list =
|
||||||
|
let before = source |> List.take index
|
||||||
|
let after = source |> List.skip (index + 1)
|
||||||
|
before @ replacement @ after
|
||||||
|
|
||||||
|
let private removeAt (index: int) (source: 'a list) : 'a list =
|
||||||
|
(source |> List.take index) @ (source |> List.skip (index + 1))
|
||||||
|
|
||||||
|
let rec private buildTree (occ: Path list) (rows: (CPat list * int) list) : DecisionTree =
|
||||||
|
match rows with
|
||||||
|
| [] -> FailNode
|
||||||
|
| (patRow, idx) :: _ when List.forall isWild patRow -> Leaf idx
|
||||||
|
| (patRow, _) :: _ ->
|
||||||
|
let col = patRow |> List.findIndex (fun p -> not (isWild p))
|
||||||
|
let occCol = List.item col occ
|
||||||
|
let present =
|
||||||
|
rows
|
||||||
|
|> List.choose (fun (r, _) ->
|
||||||
|
match List.item col r with
|
||||||
|
| CCtor(t, a, _) -> Some(t, a)
|
||||||
|
| _ -> None)
|
||||||
|
|> List.distinctBy fst
|
||||||
|
let specializeRowAt tag arity (row: CPat list, rowIdx: int) : (CPat list * int) option =
|
||||||
|
match List.item col row with
|
||||||
|
| CWild -> Some(replaceAt col (List.replicate arity CWild) row, rowIdx)
|
||||||
|
| CCtor(t, _, args) -> if t = tag then Some(replaceAt col args row, rowIdx) else None
|
||||||
|
let makeChild (tag, arity) =
|
||||||
|
let spreadOcc = replaceAt col [ for k in 0 .. arity - 1 -> occCol @ [ k ] ] occ
|
||||||
|
let subRows = rows |> List.choose (specializeRowAt tag arity)
|
||||||
|
testOfTag tag, buildTree spreadOcc subRows
|
||||||
|
let children = present |> List.map makeChild
|
||||||
|
let defaultTree =
|
||||||
|
match completeSignature (present |> List.map fst) with
|
||||||
|
| Some _ -> None
|
||||||
|
| None ->
|
||||||
|
let defaultOcc = removeAt col occ
|
||||||
|
let defaultRows =
|
||||||
|
rows
|
||||||
|
|> List.choose (fun (row, rowIdx) ->
|
||||||
|
match List.item col row with
|
||||||
|
| CWild -> Some(removeAt col row, rowIdx)
|
||||||
|
| _ -> None)
|
||||||
|
Some(buildTree defaultOcc defaultRows)
|
||||||
|
SwitchNode(occCol, children, defaultTree)
|
||||||
|
|
||||||
|
// Lowers a match into a decision tree plus, for each clause, the variable
|
||||||
|
// bindings expressed as paths into the scrutinee.
|
||||||
|
let compileMatch (patterns: Pattern list) : DecisionTree * (string * Path) list [] =
|
||||||
|
let rows = patterns |> List.mapi (fun i p -> [ toCPat p ], i)
|
||||||
|
let tree = buildTree [ [] ] rows
|
||||||
|
let bindings = patterns |> List.map (bindingsAt []) |> Array.ofList
|
||||||
|
tree, bindings
|
||||||
Reference in new issue
Block a user