Advertisement
Cukor

f# inireader

May 25th, 2014
193
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
F# 5.39 KB | None | 0 0
  1. open System
  2. open System.Runtime
  3. open System.Text
  4. open System.Text.RegularExpressions
  5. open System.IO
  6. open System.Linq
  7.  
  8. open Microsoft.FSharp.Reflection
  9.  
  10. ///Match the pattern using a cached compiled Regex
  11. let (|CompiledMatch|_|) pattern input =
  12.     if input = null then None
  13.     else
  14.         let m = Regex.Match(input, pattern, RegexOptions.Compiled)
  15.         if m.Success then Some [for x in m.Groups -> x]
  16.         else None
  17.  
  18. ///Returns the case name of the object with union type 'ty.
  19. let GetUnionCaseName (x:'a) =
  20.    match FSharpValue.GetUnionFields(x, typeof<'a>) with
  21.     | case, _ -> case.Name  
  22.  
  23. type Directive = {
  24.     name: string;
  25.     argCount: int;
  26. }
  27.  
  28. let directives = [
  29.     { name = "inherit"; argCount = 1; }
  30. ]
  31.  
  32. type DirectiveLine = {
  33.     directive: Directive;
  34.     args: seq<string>;
  35. }
  36. with
  37.     static member Parse (line: string) =
  38.         let splitLine = line.Substring(1).TrimStart().Split(" \t".ToCharArray())
  39.         let partsCount = Seq.length splitLine
  40.         if partsCount < 1 then
  41.             None
  42.         else
  43.             match Seq.tryFind (fun its -> its.name = Seq.head splitLine && (partsCount - 1) >= its.argCount ) directives with
  44.             | Some(dir) ->
  45.                 Some { directive = dir; args = Seq.take dir.argCount (Seq.skip 1 splitLine) }
  46.             | _ -> None
  47.  
  48.  
  49.  
  50.  
  51. type Line =
  52.     | Empty
  53.     | Comment
  54.     | Directive of DirectiveLine
  55.     | SectionHead of string
  56.     | KeyValuePair of string*string
  57.     with
  58.         static member ParseLine (line:string) =
  59.             match line with
  60.             | empty when String.IsNullOrWhiteSpace empty -> Empty
  61.             | comment when comment.StartsWith(";") ->
  62.                 match DirectiveLine.Parse comment with
  63.                 | Some(dir) -> Directive dir
  64.                 | None -> Comment
  65.             | CompiledMatch @"^\s*\[(\w+)\]\s*$" [_; sectionHead] -> SectionHead sectionHead.Value
  66.             | CompiledMatch @"^\s*([^=]+?)=(.+)$" [_; key; value] -> KeyValuePair (key.Value,value.Value)
  67.             | _ -> Empty
  68.  
  69. type ParserState = {
  70.     directives: list<DirectiveLine>;
  71.  
  72.     section: string option;
  73.  
  74.     globals: list<string*string>;
  75.     sections: list<string*string*string>;
  76. } with
  77.     member x.updateFromParent (parent: ParserState)=
  78.         { x with
  79.             globals = List.concat [x.globals; (List.filter (fun i -> not (List.exists (fun ex -> fst i = fst ex) x.globals)) parent.globals)]
  80.             sections = List.concat [x.sections; (List.filter (fun (sec, key, _) -> not (List.exists (fun (exsec, exkey, __) -> sec = exsec && key = exkey) x.sections)) parent.sections) ]
  81.         }
  82.     static member Default = {
  83.         directives = [];
  84.  
  85.         section = None;
  86.  
  87.         globals = [];
  88.         sections = [];
  89.     }
  90.  
  91. type IniFile ={
  92.     Globals: list<string*string>;
  93.     Sections: list<string*string*string>;
  94. } with
  95.     static member fromParser (ps: ParserState) =
  96.         {
  97.             Globals = ps.globals;
  98.             Sections = ps.sections
  99.         }
  100.  
  101. let processLine (line: string) (state: ParserState) =
  102.     printf "Parsing line: %s\n" line
  103.     let parsedLine = Line.ParseLine line
  104.     printf "Parsed line: %s\n" (GetUnionCaseName parsedLine)
  105.     match parsedLine with
  106.     | Empty -> state
  107.     | Comment -> state
  108.     | Directive d -> {state with directives = d :: state.directives }
  109.     | SectionHead sh -> { state with section = Some sh }
  110.     | KeyValuePair (k,p) ->
  111.         match state.section with
  112.         | Some(sect) -> {state with sections = (sect, k, p) :: state.sections }
  113.         | _ -> {state with globals = (k, p) :: state.globals }
  114.  
  115. let processLines (lines: seq<string>): ParserState =
  116.     Seq.fold (fun ps line -> processLine line ps) ParserState.Default lines
  117.  
  118. let readSingleFile (filename: string) =
  119.     processLines (File.ReadLines(filename, Encoding.UTF8))
  120.  
  121. let resolveFileName basePath relativeOrAbsolute =
  122.     Path.Combine(Path.GetDirectoryName basePath, relativeOrAbsolute)
  123.  
  124. //DFS topological sort for resolving
  125. let rec readIniInternal
  126.     (filesproceesed: Map<string, ParserState>)
  127.     (filename: string)
  128.     (parents: list<string>)
  129.     (maxRequestedReferenceDepth:int) =
  130.         if Seq.length parents >= maxRequestedReferenceDepth then
  131.             failwith "Recursion depth exceeded"
  132.         else
  133.         if List.exists (fun i -> i = filename) parents then
  134.             failwithf "Recursion encountered on path %s in file %s" (String.Join(", ", parents)) filename
  135.         else
  136.             match Map.tryFind filename filesproceesed with
  137.             | Some(inifile) -> (inifile, filesproceesed)
  138.             | None ->
  139.                 let parser = readSingleFile filename
  140.                 parser.directives
  141.                 |> List.filter (fun dir -> dir.directive.name = "inherit")
  142.                 |> List.fold (fun (upFile, processed) inhDir ->
  143.                     let resolvedFileName = resolveFileName filename (inhDir.args.First())
  144.                     let procedfle, allprocd = readIniInternal processed resolvedFileName (filename :: parents) maxRequestedReferenceDepth
  145.                     (parser.updateFromParent procedfle, Map.add resolvedFileName procedfle allprocd)
  146.                     ) (parser, filesproceesed)
  147.  
  148. let readIni (filename: string): IniFile =
  149.     let maxDepth = 12
  150.     IniFile.fromParser (fst (readIniInternal Map.empty (resolveFileName Environment.CurrentDirectory filename) List.empty maxDepth))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement