Primero permítanme pedir disculpas por la magnitud de este problema, pero realmente estoy tratando de pensar funcionalmente y este es uno de los problemas más desafiantes con los que he tenido que trabajar.Cómo escribir un archivo funcional "escáner"
Quería obtener algunas sugerencias sobre cómo podría manejar un problema que tengo de manera funcional, particularmente en F #. Estoy escribiendo un programa para ir a través de una lista de directorios y usar una lista de patrones regex para filtrar la lista de archivos recuperados de los directorios y usar una segunda lista de patrones regex para encontrar coincidencias en el texto de los archivos recuperados. Quiero que esto devuelva el nombre de archivo, el índice de línea, el índice de columna, el patrón y el valor coincidente para cada fragmento de texto que coincida con un patrón de expresión regular determinado. Además, las excepciones deben registrarse y existen 3 posibles escenarios de excepciones: no se puede abrir el directorio, no se puede abrir el archivo, no se puede leer el contenido del archivo. El requisito final de esto es el volumen de archivos "escaneados" para los partidos podría ser muy grande, por lo que todo esto tiene que ser flojo. No estoy demasiado preocupado por una solución funcional "pura" tanto como me interesa una "buena" solución que lea bien y funcione bien. Un desafío final es hacer interoperabilidad con C# porque me gustaría utilizar las herramientas de winform para adjuntar este algoritmo a una interfaz de usuario. Aquí está mi primer intento y con suerte esto aclarará el problema:
open System.Text.RegularExpressions
open System.IO
type Reader<'t, 'a> = 't -> 'a //=M['a], result varies
let returnM x _ = x
let map f m = fun t -> t |> m |> f
let apply f m = fun t -> t |> m |> (t |> f)
let bind f m = fun t -> t |> (t |> m |> f)
let Scanner dirs =
returnM dirs
|> apply (fun dirExHandler ->
Seq.collect (fun directory ->
try
Directory.GetFiles(directory, "*", SearchOption.AllDirectories)
with | e ->
dirExHandler e directory
Array.empty))
|> map (fun filenames ->
returnM filenames
|> apply (fun (filenamepatterns, lineExHandler, fileExHandler) ->
Seq.filter (fun filename ->
filenamepatterns |> Seq.exists (fun pattern ->
let regex = new Regex(pattern)
regex.IsMatch(filename)))
>> Seq.map (fun filename ->
let fileinfo = new FileInfo(filename)
try
use reader = fileinfo.OpenText()
Seq.unfold (fun ((reader : StreamReader), index) ->
if not reader.EndOfStream then
try
let line = reader.ReadLine()
Some((line, index), (reader, index + 1))
with | e ->
lineExHandler e filename index
None
else
None) (reader, 0)
|> (fun lines -> (filename, lines))
with | e ->
fileExHandler e filename
(filename, Seq.empty))
>> (fun files ->
returnM files
|> apply (fun contentpatterns ->
Seq.collect (fun file ->
let filename, lines = file
lines |>
Seq.collect (fun line ->
let content, index = line
contentpatterns
|> Seq.collect (fun pattern ->
let regex = new Regex(pattern)
regex.Matches(content)
|> (Seq.cast<Match>
>> Seq.map (fun contentmatch ->
(filename,
index,
contentmatch.Index,
pattern,
contentmatch.Value))))))))))
Gracias por cualquier aportación.
Actualizado - aquí es cualquier solución actualizada en base a la retroalimentación que he recibido:
open System.Text.RegularExpressions
open System.IO
type ScannerConfiguration = {
FileNamePatterns : seq<string>
ContentPatterns : seq<string>
FileExceptionHandler : exn -> string -> unit
LineExceptionHandler : exn -> string -> int -> unit
DirectoryExceptionHandler : exn -> string -> unit }
let scanner specifiedDirectories (configuration : ScannerConfiguration) = seq {
let ToCachedRegexList = Seq.map (fun pattern -> new Regex(pattern)) >> Seq.cache
let contentRegexes = configuration.ContentPatterns |> ToCachedRegexList
let filenameRegexes = configuration.FileNamePatterns |> ToCachedRegexList
let getLines exHandler reader =
Seq.unfold (fun ((reader : StreamReader), index) ->
if not reader.EndOfStream then
try
let line = reader.ReadLine()
Some((line, index), (reader, index + 1))
with | e -> exHandler e index; None
else
None) (reader, 0)
for specifiedDirectory in specifiedDirectories do
let files =
try Directory.GetFiles(specifiedDirectory, "*", SearchOption.AllDirectories)
with e -> configuration.DirectoryExceptionHandler e specifiedDirectory; [||]
for file in files do
if filenameRegexes |> Seq.exists (fun (regex : Regex) -> regex.IsMatch(file)) then
let lines =
let fileinfo = new FileInfo(file)
try
use reader = fileinfo.OpenText()
reader |> getLines (fun e index -> configuration.LineExceptionHandler e file index)
with | e -> configuration.FileExceptionHandler e file; Seq.empty
for line in lines do
let content, index = line
for contentregex in contentRegexes do
for mmatch in content |> contentregex.Matches do
yield (file, index, mmatch.Index, contentregex.ToString(), mmatch.Value) }
Una vez más, cualquier entrada es bienvenido.
¿Has visto analizadores sintácticos como Parsec? –
Esto es mucho texto. Intenta dividirlo para que sea más fácil de leer. – Marcin
Simplemente usaría una interfaz y Object Expression para crear una instancia y exponerla al código C#. –