csharpfftfsharpintegrationinterpolationlinear-algebramathdifferentiationmatrixnumericsrandomregressionstatisticsmathnet
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
16 KiB
401 lines
16 KiB
(* (c) Microsoft Corporation 2005-2008. *)
|
|
|
|
#light
|
|
#nowarn "35" // This construct is deprecated: This form of object expression is deprecated. Use 'member this.MemberName ... = ...' to define member implementations in object expressions.
|
|
#nowarn "62" // This construct is for compatibility with OCaml.
|
|
|
|
open System.Collections.Generic
|
|
open Microsoft.FSharp.Text
|
|
open Microsoft.FSharp.Collections
|
|
open Internal.Utilities
|
|
open Internal.Utilities.Text.Lexing
|
|
|
|
let (|KeyValue|) (kvp:KeyValuePair<_,_>) = kvp.Key,kvp.Value
|
|
|
|
type Ident = string
|
|
type Code = string * Position
|
|
|
|
type Alphabet = uint32
|
|
|
|
let Eof : Alphabet = 0xFFFFFFFEu
|
|
let Epsilon : Alphabet = 0xFFFFFFFFu
|
|
|
|
|
|
let unicode = ref false
|
|
|
|
let unicodeCategories =
|
|
dict
|
|
[| "Pe", System.Globalization.UnicodeCategory.ClosePunctuation; // (Pe)
|
|
"Pc", System.Globalization.UnicodeCategory.ConnectorPunctuation; // (Pc)
|
|
"Cc", System.Globalization.UnicodeCategory.Control; // (Cc)
|
|
"Sc", System.Globalization.UnicodeCategory.CurrencySymbol; // (Sc)
|
|
"Pd", System.Globalization.UnicodeCategory.DashPunctuation; // (Pd)
|
|
"Nd", System.Globalization.UnicodeCategory.DecimalDigitNumber; // (Nd)
|
|
"Me", System.Globalization.UnicodeCategory.EnclosingMark; // (Me)
|
|
"Pf", System.Globalization.UnicodeCategory.FinalQuotePunctuation; // (Pf)
|
|
"Cf", enum 15; //System.Globalization.UnicodeCategory.Format; // (Cf)
|
|
"Pi", System.Globalization.UnicodeCategory.InitialQuotePunctuation; // (Pi)
|
|
"Nl", System.Globalization.UnicodeCategory.LetterNumber; // (Nl)
|
|
"Zl", System.Globalization.UnicodeCategory.LineSeparator; // (Zl)
|
|
"Ll", System.Globalization.UnicodeCategory.LowercaseLetter; // (Ll)
|
|
"Sm", System.Globalization.UnicodeCategory.MathSymbol; // (Sm)
|
|
"Lm", System.Globalization.UnicodeCategory.ModifierLetter; // (Lm)
|
|
"Sk", System.Globalization.UnicodeCategory.ModifierSymbol; // (Sk)
|
|
"Mn", System.Globalization.UnicodeCategory.NonSpacingMark; // (Mn)
|
|
"Ps", System.Globalization.UnicodeCategory.OpenPunctuation; // (Ps)
|
|
"Lo", System.Globalization.UnicodeCategory.OtherLetter; // (Lo)
|
|
"Cn", System.Globalization.UnicodeCategory.OtherNotAssigned; // (Cn)
|
|
"No", System.Globalization.UnicodeCategory.OtherNumber; // (No)
|
|
"Po", System.Globalization.UnicodeCategory.OtherPunctuation; // (Po)
|
|
"So", System.Globalization.UnicodeCategory.OtherSymbol; // (So)
|
|
"Zp", System.Globalization.UnicodeCategory.ParagraphSeparator; // (Zp)
|
|
"Co", System.Globalization.UnicodeCategory.PrivateUse; // (Co)
|
|
"Zs", System.Globalization.UnicodeCategory.SpaceSeparator; // (Zs)
|
|
"Mc", System.Globalization.UnicodeCategory.SpacingCombiningMark; // (Mc)
|
|
"Cs", System.Globalization.UnicodeCategory.Surrogate; // (Cs)
|
|
"Lt", System.Globalization.UnicodeCategory.TitlecaseLetter; // (Lt)
|
|
"Lu", System.Globalization.UnicodeCategory.UppercaseLetter; // (Lu)
|
|
|]
|
|
|
|
let NumUnicodeCategories = unicodeCategories.Count
|
|
let _ = assert (NumUnicodeCategories = 30) // see table interpreter
|
|
let encodedUnicodeCategoryBase = 0xFFFFFF00u
|
|
let EncodeUnicodeCategoryIndex(idx:int) = encodedUnicodeCategoryBase + uint32 idx
|
|
let EncodeUnicodeCategory(s:string) =
|
|
if not (!unicode) then
|
|
failwith "unicode category classes may only be used if --unicode is specified";
|
|
if unicodeCategories.ContainsKey(s) then
|
|
EncodeUnicodeCategoryIndex (int32 unicodeCategories.[s])
|
|
else
|
|
failwithf "invalid Unicode category: '%s'" s
|
|
|
|
let IsUnicodeCategory(x:Alphabet) = (encodedUnicodeCategoryBase <= x) && (x < encodedUnicodeCategoryBase + uint32 NumUnicodeCategories)
|
|
let UnicodeCategoryIndex(x:Alphabet) = (x - encodedUnicodeCategoryBase)
|
|
|
|
let numLowUnicodeChars = 128
|
|
let _ = assert (numLowUnicodeChars = 128) // see table interpreter
|
|
let specificUnicodeChars = new Dictionary<_,_>()
|
|
let specificUnicodeCharsDecode = new Dictionary<_,_>()
|
|
let EncodeChar(c:char) =
|
|
let x = System.Convert.ToUInt32 c
|
|
if !unicode then
|
|
if x < uint32 numLowUnicodeChars then x
|
|
else
|
|
if not(specificUnicodeChars.ContainsKey(c)) then
|
|
let idx = uint32 numLowUnicodeChars + uint32 specificUnicodeChars.Count
|
|
specificUnicodeChars.[c] <- idx
|
|
specificUnicodeCharsDecode.[idx] <- c
|
|
specificUnicodeChars.[c]
|
|
else
|
|
if x >= 256u then failwithf "the Unicode character '%c' may not be used unless --unicode is specified" c;
|
|
x
|
|
let DecodeChar(x:Alphabet) =
|
|
if !unicode then
|
|
if x < uint32 numLowUnicodeChars then System.Convert.ToChar x
|
|
else specificUnicodeCharsDecode.[x]
|
|
else
|
|
if x >= 256u then failwithf "the Unicode character '%x' may not be used unless --unicode is specified" x;
|
|
System.Convert.ToChar x
|
|
|
|
|
|
|
|
let NumSpecificUnicodeChars() = specificUnicodeChars.Count
|
|
let GetSpecificUnicodeChars() =
|
|
specificUnicodeChars
|
|
|> Seq.sortBy (fun (KeyValue(k,v)) -> v)
|
|
|> Seq.map (fun (KeyValue(k,v)) -> k)
|
|
|
|
let GetSingleCharAlphabet() =
|
|
if !unicode
|
|
then Set.of_list [ for c in 0..numLowUnicodeChars-1 do yield (char c)
|
|
for c in GetSpecificUnicodeChars() do yield c ]
|
|
else Set.of_list [ for x in 0..255 -> (char x) ]
|
|
|
|
let GetAlphabet() =
|
|
if !unicode
|
|
then Set.of_list [ for c in GetSingleCharAlphabet() do yield EncodeChar c
|
|
for uc in 0 .. NumUnicodeCategories-1 do yield EncodeUnicodeCategoryIndex uc ]
|
|
else Set.of_list [ for c in GetSingleCharAlphabet() do yield EncodeChar c ]
|
|
|
|
|
|
//let DecodeAlphabet (x:Alphabet) = System.Convert.ToChar(x)
|
|
|
|
(*
|
|
for i in 0 .. 65535 do
|
|
let c = char i
|
|
if System.Char.GetUnicodeCategory c = System.Globalization.UnicodeCategory.PrivateUse then
|
|
printfn "i = %x" i
|
|
*)
|
|
|
|
type Spec =
|
|
{ TopCode: Code;
|
|
Macros: (Ident * Regexp) list;
|
|
Rules: (Ident * Ident list * Clause list) list;
|
|
BottomCode: Code }
|
|
and Clause = Regexp * Code
|
|
and Regexp =
|
|
| Alt of Regexp list
|
|
| Seq of Regexp list
|
|
| Inp of Input
|
|
| Star of Regexp
|
|
| Macro of Ident
|
|
and Input =
|
|
| Alphabet of Alphabet
|
|
| UnicodeCategory of string
|
|
| Any
|
|
| NotCharSet of Set<Alphabet>
|
|
|
|
type NodeId = int
|
|
|
|
type NfaNode =
|
|
{ Id: NodeId;
|
|
Name: string;
|
|
Transitions: Dictionary<Alphabet, NfaNode list>;
|
|
Accepted: (int * int) list }
|
|
|
|
type DfaNode =
|
|
{ Id: int;
|
|
Name: string;
|
|
mutable Transitions: (Alphabet * DfaNode) list;
|
|
Accepted: (int * int) list }
|
|
|
|
type MultiMap<'a,'b> = Dictionary<'a,'b list>
|
|
let LookupMultiMap (trDict:MultiMap<_,_>) a =
|
|
if trDict.ContainsKey(a) then trDict.[a] else []
|
|
|
|
let AddToMultiMap (trDict:MultiMap<_,_>) a b =
|
|
let prev = LookupMultiMap trDict a
|
|
trDict.[a] <- b::prev
|
|
|
|
type NfaNodeMap() =
|
|
let map = new Dictionary<int,NfaNode>(100)
|
|
member x.Item with get(nid) = map.[nid]
|
|
member x.Count = map.Count
|
|
|
|
member x.NewNfaNode(trs,ac) =
|
|
let nodeId = map.Count+1 // ID zero is reserved
|
|
let trDict = new Dictionary<_,_>(List.length trs)
|
|
for (a,b) in trs do
|
|
AddToMultiMap trDict a b
|
|
|
|
let node = {new NfaNode with Id=nodeId and Name=string nodeId and Transitions=trDict and Accepted=ac}
|
|
map.[nodeId] <-node;
|
|
node
|
|
|
|
let LexerStateToNfa (macros: Map<string,_>) (clauses: Clause list) =
|
|
|
|
/// Table allocating node ids
|
|
let nfaNodeMap = new NfaNodeMap()
|
|
|
|
/// Compile a regular expression into the NFA
|
|
let rec CompileRegexp re dest =
|
|
match re with
|
|
| Alt res ->
|
|
let trs = res |> List.map (fun re -> (Epsilon,CompileRegexp re dest))
|
|
nfaNodeMap.NewNfaNode(trs,[])
|
|
| Seq res ->
|
|
List.fold_right (CompileRegexp) res dest
|
|
| Inp (Alphabet c) ->
|
|
nfaNodeMap.NewNfaNode([(c, dest)],[])
|
|
|
|
| Star re ->
|
|
let nfaNode = nfaNodeMap.NewNfaNode([(Epsilon, dest)],[])
|
|
let sre = CompileRegexp re nfaNode
|
|
AddToMultiMap nfaNode.Transitions Epsilon sre
|
|
nfaNodeMap.NewNfaNode([(Epsilon,sre); (Epsilon,dest)],[])
|
|
| Macro m ->
|
|
if not (macros.ContainsKey(m)) then failwith ("The macro "^m^" is not defined");
|
|
CompileRegexp (macros.[m]) dest
|
|
|
|
// These cases unwind the difficult cases in the syntax that rely on knowing the
|
|
// entire alphabet.
|
|
//
|
|
// Note we've delayed the expension of these until we've worked out all the 'special' Unicode characters
|
|
// mentioned in the entire lexer spec, i.e. we wait until GetAlphabet returns a reliable and stable answer.
|
|
| Inp (UnicodeCategory uc) ->
|
|
let re = Alt([ yield Inp(Alphabet(EncodeUnicodeCategory uc))
|
|
// Also include any specific characters in this category
|
|
for c in GetSingleCharAlphabet() do
|
|
if System.Char.GetUnicodeCategory(c) = unicodeCategories.[uc] then
|
|
yield Inp(Alphabet(EncodeChar(c))) ])
|
|
CompileRegexp re dest
|
|
|
|
| Inp Any ->
|
|
let re = Alt([ for n in GetAlphabet() do yield Inp(Alphabet(n)) ])
|
|
CompileRegexp re dest
|
|
|
|
| Inp (NotCharSet chars) ->
|
|
let re = Alt [ // Include any characters from those in the alphabet besides those that are not immediately excluded
|
|
for c in GetSingleCharAlphabet() do
|
|
let ec = EncodeChar c
|
|
if not (chars.Contains(ec)) then
|
|
yield Inp(Alphabet(ec))
|
|
|
|
// Include all unicode categories
|
|
// That is, negations _only_ exclude precisely the given set of characters. You can't
|
|
// exclude whole classes of characters as yet
|
|
if !unicode then
|
|
let ucs = chars |> Set.map(DecodeChar >> System.Char.GetUnicodeCategory)
|
|
for KeyValue(nm,uc) in unicodeCategories do
|
|
//if ucs.Contains(uc) then
|
|
// do printfn "warning: the unicode category '\\%s' ('%s') is automatically excluded by this character set negation. Consider adding this to the negation." nm (uc.ToString())
|
|
// yield! []
|
|
//else
|
|
yield Inp(Alphabet(EncodeUnicodeCategory nm))
|
|
]
|
|
CompileRegexp re dest
|
|
|
|
let actions = new System.Collections.Generic.List<_>()
|
|
|
|
/// Compile an acceptance of a regular expression into the NFA
|
|
let sTrans macros nodeId (regexp,code) =
|
|
let actionId = actions.Count
|
|
actions.Add(code)
|
|
let sAccept = nfaNodeMap.NewNfaNode([],[(nodeId,actionId)])
|
|
CompileRegexp regexp sAccept
|
|
|
|
let trs = clauses |> List.mapi (fun n x -> (Epsilon,sTrans macros n x))
|
|
let nfaStartNode = nfaNodeMap.NewNfaNode(trs,[])
|
|
nfaStartNode,(actions |> Seq.readonly), nfaNodeMap
|
|
|
|
// TODO: consider a better representation here.
|
|
type NfaNodeIdSetBuilder = HashSet<NodeId>
|
|
|
|
type NfaNodeIdSet(nodes: NfaNodeIdSetBuilder) =
|
|
let s = nodes |> Seq.to_array |> Array.sort
|
|
member x.Representation = s
|
|
member x.Elements = s
|
|
member x.Fold f z = Array.fold_left f z s
|
|
interface System.IComparable with
|
|
member x.CompareTo(y:obj) =
|
|
let y = (y :?> NfaNodeIdSet)
|
|
let xr = x.Representation
|
|
let yr = y.Representation
|
|
let c = compare xr.Length yr.Length
|
|
if c <> 0 then c else
|
|
let n = yr.Length
|
|
let rec go i =
|
|
if i >= n then 0 else
|
|
let c = compare xr.[i] yr.[i]
|
|
if c <> 0 then c else
|
|
go (i+1)
|
|
go 0
|
|
|
|
override x.Equals(y:obj) =
|
|
match y with
|
|
| :? NfaNodeIdSet as y ->
|
|
let xr = x.Representation
|
|
let yr = y.Representation
|
|
let n = yr.Length
|
|
xr.Length = n &&
|
|
(let rec go i = (i < n) && xr.[i] = yr.[i] && go (i+1)
|
|
go 0)
|
|
| _ -> false
|
|
member x.IsEmpty = (s.Length = 0)
|
|
member x.Iterate f = s |> Array.iter f
|
|
|
|
type NodeSetSet = Set<NfaNodeIdSet>
|
|
|
|
let newDfaNodeId =
|
|
let i = ref 0
|
|
fun () -> let res = !i in incr i; res
|
|
|
|
let NfaToDfa (nfaNodeMap:NfaNodeMap) nfaStartNode =
|
|
let numNfaNodes = nfaNodeMap.Count
|
|
let rec EClosure1 (acc:NfaNodeIdSetBuilder) (n:NfaNode) =
|
|
if not (acc.Contains(n.Id)) then
|
|
acc.Add(n.Id);
|
|
if n.Transitions.ContainsKey(Epsilon) then
|
|
match n.Transitions.[Epsilon] with
|
|
| [] -> () // this Clause is an optimization - the list is normally empty
|
|
| tr ->
|
|
//printfn "n.Id = %A, #Epsilon = %d" n.Id tr.Length
|
|
tr |> List.iter (EClosure1 acc)
|
|
|
|
let EClosure (moves:list<NodeId>) =
|
|
let acc = new NfaNodeIdSetBuilder(10)
|
|
for i in moves do
|
|
EClosure1 acc nfaNodeMap.[i];
|
|
new NfaNodeIdSet(acc)
|
|
|
|
// Compute all the immediate one-step moves for a set of NFA states, as a dictionary
|
|
// mapping inputs to destination lists
|
|
let ComputeMoves (nset:NfaNodeIdSet) =
|
|
let moves = new MultiMap<_,_>()
|
|
nset.Iterate(fun nodeId ->
|
|
for (KeyValue(inp,dests)) in nfaNodeMap.[nodeId].Transitions do
|
|
if inp <> Epsilon then
|
|
match dests with
|
|
| [] -> () // this Clause is an optimization - the list is normally empty
|
|
| tr -> tr |> List.iter(fun dest -> AddToMultiMap moves inp dest.Id))
|
|
moves
|
|
|
|
let acc = new NfaNodeIdSetBuilder(10)
|
|
EClosure1 acc nfaStartNode;
|
|
let nfaSet0 = new NfaNodeIdSet(acc)
|
|
|
|
let dfaNodes = ref (Map<NfaNodeIdSet,DfaNode>.Empty)
|
|
|
|
let GetDfaNode nfaSet =
|
|
if (!dfaNodes).ContainsKey(nfaSet) then
|
|
(!dfaNodes).[nfaSet]
|
|
else
|
|
let dfaNode =
|
|
{ Id= newDfaNodeId();
|
|
Name = nfaSet.Fold (fun s nid -> nfaNodeMap.[nid].Name^"-"^s) "";
|
|
Transitions=[];
|
|
Accepted= nfaSet.Elements
|
|
|> Seq.map (fun nid -> nfaNodeMap.[nid].Accepted)
|
|
|> List.concat }
|
|
//Printf.printfn "id = %d" dfaNode.Id;
|
|
|
|
dfaNodes := (!dfaNodes).Add(nfaSet,dfaNode);
|
|
dfaNode
|
|
|
|
let workList = ref [nfaSet0]
|
|
let doneSet = ref NodeSetSet.Empty
|
|
|
|
//let count = ref 0
|
|
let rec Loop () =
|
|
match !workList with
|
|
| [] -> ()
|
|
| nfaSet ::t ->
|
|
workList := t;
|
|
if (!doneSet).Contains(nfaSet) then
|
|
Loop ()
|
|
else
|
|
let moves = ComputeMoves nfaSet
|
|
for (KeyValue(inp,movesForInput)) in moves do
|
|
assert (inp <> Epsilon);
|
|
let moveSet = EClosure movesForInput;
|
|
if not moveSet.IsEmpty then
|
|
//incr count
|
|
let dfaNode = GetDfaNode nfaSet
|
|
dfaNode.Transitions <- (inp, GetDfaNode moveSet) :: dfaNode.Transitions;
|
|
(* Printf.printf "%d (%s) : %s --> %d (%s)\n" dfaNode.Id dfaNode.Name (match inp with EncodeChar c -> String.make 1 c | LEof -> "eof") moveSetDfaNode.Id moveSetDfaNode.Name;*)
|
|
workList := moveSet :: !workList;
|
|
|
|
doneSet := (!doneSet).Add(nfaSet);
|
|
|
|
|
|
Loop()
|
|
Loop();
|
|
//Printf.printfn "count = %d" !count;
|
|
let ruleStartNode = GetDfaNode nfaSet0
|
|
let ruleNodes =
|
|
(!dfaNodes)
|
|
|> Seq.map (fun kvp -> kvp.Value)
|
|
|> Seq.to_list
|
|
|> List.sortBy (fun s -> s.Id)
|
|
ruleStartNode,ruleNodes
|
|
|
|
let Compile spec =
|
|
List.fold_right
|
|
(fun (name,args,clauses) (perRuleData,dfaNodes) ->
|
|
let nfa, actions, nfaNodeMap = LexerStateToNfa (Map.of_list spec.Macros) clauses
|
|
let ruleStartNode, ruleNodes = NfaToDfa nfaNodeMap nfa
|
|
//Printf.printfn "name = %s, ruleStartNode = %O" name ruleStartNode.Id;
|
|
(ruleStartNode,actions) :: perRuleData, ruleNodes @ dfaNodes)
|
|
spec.Rules
|
|
([],[])
|
|
|
|
|