Program.fs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. module Program
  2. open Argu
  3. open FsharpMyExtension
  4. open FsharpMyExtension.Either
  5. type FilePath = string
  6. type Txt2qspConfig = { Path: string; Args: string }
  7. let qspSourceExt = ".qsps"
  8. let changeExtensionToQsp (path:FilePath) =
  9. System.IO.Path.ChangeExtension(path, qspSourceExt)
  10. module ThreadSafePrint =
  11. let mail = MailboxProcessor.Start (fun agent ->
  12. let rec loop () =
  13. async {
  14. let! msg = agent.Receive()
  15. printfn "%s" msg
  16. return! loop ()
  17. }
  18. loop ()
  19. )
  20. let printfn fmt = Printf.ksprintf mail.Post fmt
  21. let decodeGame config (src:FilePath) (dst:FilePath) =
  22. if System.IO.File.Exists config.Path then
  23. let args = sprintf "\"%s\" \"%s\" D %s" src dst config.Args
  24. let startProcString path args =
  25. ThreadSafePrint.printfn "decoding: `%s %s`" path args
  26. let drivenOutput = new System.Text.StringBuilder()
  27. Proc.startProc (fun e ->
  28. drivenOutput.AppendLine(e) |> ignore
  29. ) path args
  30. |> fun code -> code, drivenOutput.ToString()
  31. let code, output = startProcString config.Path args
  32. if code = 0 then
  33. Right dst
  34. else
  35. Left output
  36. else
  37. Left (sprintf "txt2qsp not found in '%s'" config.Path)
  38. let doFile txt2qspConfig updateSourceIfExists src =
  39. let dst = changeExtensionToQsp src
  40. if System.IO.File.Exists dst then
  41. if updateSourceIfExists then
  42. decodeGame txt2qspConfig src dst
  43. else
  44. Right (dst:FilePath)
  45. else
  46. decodeGame txt2qspConfig src dst
  47. let threadsExec threads fn xs =
  48. if threads > 1 then
  49. xs
  50. |> Seq.map (fun x -> async { return fn x })
  51. |> fun xs -> Async.Parallel(xs, threads)
  52. |> Async.RunSynchronously
  53. else
  54. xs |> Seq.map fn |> Array.ofSeq
  55. let doDir txt2qspConfig updateSourceIfExists threads dir =
  56. // TODO:
  57. // let xs =
  58. // System.IO.Directory.EnumerateFiles(dir, "*.*", System.IO.SearchOption.AllDirectories)
  59. // |> Seq.filter (fun path ->
  60. // match (System.IO.Path.GetExtension path).ToLower() with
  61. // | ".qsp" | ".qsps" -> true
  62. // | _ -> false
  63. // )
  64. // |> Seq.groupBy (fun x ->
  65. // System.IO.Path.GetFileNameWithoutExtension x
  66. // )
  67. // if use the "*.qsp" mask, it will capture the ".qsps" files as well
  68. let xs =
  69. System.IO.Directory.EnumerateFiles(dir, "*.*", System.IO.SearchOption.AllDirectories)
  70. |> Seq.filter
  71. (System.IO.Path.GetExtension
  72. >> String.toLower
  73. >> fun ext -> ext = ".qsp" || ext = ".gam")
  74. xs
  75. |> threadsExec threads (doFile txt2qspConfig updateSourceIfExists)
  76. type CliArguments =
  77. | Working_Directory of path:FilePath
  78. | Txt2qsp of path:FilePath * args:string
  79. | Source_Path of path:FilePath
  80. | UpdateSourceIfExists
  81. | Threads of int
  82. interface IArgParserTemplate with
  83. member s.Usage =
  84. match s with
  85. | Working_Directory _ -> "specify a working directory."
  86. | Txt2qsp _ -> "specify a txt2gam (path : args)."
  87. | Source_Path _ -> "path to encoded game (.qps) or decoded source game (.qsps)."
  88. | UpdateSourceIfExists -> "decodes the source, even if it exists."
  89. | Threads _ -> "number of threads per file, by default is 1."
  90. module Parser =
  91. open FParsec
  92. open Qsp.Parser.Generic
  93. open Qsp.Parser.Main
  94. let parserStmt str =
  95. let emptyState =
  96. { emptyState with PStmts = pstmts }
  97. let p =
  98. spaces >>. pstmt
  99. .>> (getPosition >>= fun p ->
  100. updateUserState (fun st ->
  101. { st with LastSymbolPos = p}))
  102. runParserOnString p
  103. emptyState
  104. ""
  105. str
  106. open Qsp.Ast
  107. let patternMatching pattern =
  108. let rec stmtsOrRawEqual (acc:_ list) x =
  109. match x with
  110. | StaticStmts xs ->
  111. stmtsMatcher acc xs
  112. | Raw _ -> acc
  113. and lineKindEqual acc x =
  114. match x with
  115. | HyperLinkKind(stmtOrRaw, xs) ->
  116. List.fold lineEqual (stmtsOrRawEqual acc stmtOrRaw) xs
  117. | HyperLinkKind _
  118. | ExprKind _
  119. | StringKind _ -> acc
  120. and lineEqual acc (xs:Qsp.Ast.Line) =
  121. List.fold lineKindEqual acc xs
  122. and valueEqual acc x =
  123. match x with
  124. | String lines ->
  125. List.fold lineEqual acc lines
  126. | Int _ -> acc
  127. and exprEqual acc x =
  128. match x with
  129. | Val x ->
  130. valueEqual acc x
  131. | Arr _
  132. | Expr _
  133. | Func _
  134. | UnarExpr _
  135. | Var _
  136. -> acc
  137. and stmtsMatcher (acc:list<PosStatement>) stmts =
  138. stmts
  139. |> List.fold (fun acc stmt ->
  140. if pattern = stmt then
  141. stmt::acc
  142. else
  143. let _, stmt = stmt
  144. match stmt with
  145. | Assign(_, _, expr) ->
  146. exprEqual acc expr
  147. | Proc(_, exprs) ->
  148. List.fold exprEqual acc exprs
  149. | Exit -> acc
  150. | Act(exprs, body) ->
  151. let acc = List.fold exprEqual acc exprs
  152. stmtsMatcher acc body
  153. | AssignCode(_, body) -> stmtsMatcher acc body
  154. | If(expr, thenBody, elseBody) ->
  155. let acc = exprEqual acc expr
  156. let acc = stmtsMatcher acc thenBody
  157. stmtsMatcher acc elseBody
  158. | Label(_) -> acc
  159. | Comment(_) -> acc
  160. ) acc
  161. stmtsMatcher []
  162. let parse patternRaw locs =
  163. match Parser.parserStmt patternRaw with
  164. | FParsec.CharParsers.Success(pattern, st, _) ->
  165. // printfn "pattern:\n%A\n" pattern
  166. locs
  167. |> List.map (fun (Location (locName, loc)) ->
  168. locName, patternMatching pattern loc
  169. )
  170. |> Right
  171. | FParsec.CharParsers.Failure(err, st, _) ->
  172. Left err
  173. [<EntryPoint>]
  174. let main argv =
  175. let parser = ArgumentParser.Create<CliArguments>(programName = "Utility.exe")
  176. let enc = System.Text.Encoding.UTF8
  177. let results = parser.Parse argv
  178. let results = results.GetAllResults()
  179. let updateSourceIfExists =
  180. results |> List.exists ((=) UpdateSourceIfExists)
  181. let txt2qspConfig =
  182. results
  183. |> List.tryPick (function
  184. | Txt2qsp(path, args) ->
  185. { Path = path; Args = args }
  186. |> Some
  187. | _ -> None
  188. ) |> Either.ofOption "not defined --txt2qsp"
  189. let threads =
  190. results
  191. |> List.tryPick (function
  192. | Threads i -> Some i
  193. | _ -> None
  194. ) |> Option.defaultValue 1
  195. let folder =
  196. results
  197. |> List.tryPick (function
  198. | Working_Directory dir -> Some dir
  199. | _ -> None
  200. )
  201. |> Either.ofOption "not defined --working-directory"
  202. let folderExec folder =
  203. txt2qspConfig
  204. |> Either.map (fun config ->
  205. doDir config updateSourceIfExists threads folder
  206. )
  207. |> Either.either (Left >> Array.singleton) id
  208. let path =
  209. results
  210. |> List.tryPick (function
  211. | Source_Path src -> Some src
  212. | _ -> None
  213. )
  214. |> Either.ofOption "not defined --source-path"
  215. let pathExec () =
  216. path
  217. |> Either.bind (fun src ->
  218. match String.toLower (System.IO.Path.GetExtension src) with
  219. | ".qsp" | ".gam" ->
  220. txt2qspConfig
  221. |> Either.bind (fun config ->
  222. doFile config updateSourceIfExists src
  223. )
  224. | ".qsps" -> Right src // TODO: updateSourceIfExists
  225. | ext ->
  226. Left (sprintf "expected .qsp or .qsps extension but %s\nin\n%s" ext src)
  227. )
  228. let getPattern () =
  229. let getPattern () =
  230. // the ideal solution would be to parse the text on the fly, but need to somehow define the boundaries of Stream
  231. let terminator = ";;"
  232. printfn "input statement (`%s` — end):" terminator
  233. let rec f acc =
  234. let line = System.Console.ReadLine()
  235. let acc = line::acc
  236. if line.Contains terminator then
  237. List.rev acc |> System.String.Concat
  238. else
  239. f acc
  240. f []
  241. let rec f () =
  242. let patternRaw = getPattern ()
  243. match Parser.parserStmt patternRaw with
  244. | FParsec.CharParsers.Success(pattern, st, _) ->
  245. pattern
  246. | FParsec.CharParsers.Failure(err, st, _) ->
  247. printfn "%s" err
  248. f ()
  249. f ()
  250. let parse locs =
  251. let pattern = getPattern ()
  252. locs
  253. |> List.map (fun (Location (locName, loc)) ->
  254. locName, patternMatching pattern loc
  255. )
  256. let all () =
  257. match folder with
  258. | Right folder ->
  259. let pattern = getPattern ()
  260. folderExec folder
  261. |> threadsExec threads
  262. (Either.bind (fun path ->
  263. ThreadSafePrint.printfn "parse: %s" path
  264. match Qsp.Parser.Main.startOnFile enc path with
  265. | FParsec.CharParsers.Success(locs, st, _) ->
  266. locs
  267. |> List.choose (fun (Location (locName, loc)) ->
  268. match patternMatching pattern loc with
  269. | [] -> None
  270. | xs -> Some(locName, xs)
  271. )
  272. |> fun res -> Right (path, res)
  273. | FParsec.CharParsers.Failure(errMsg, err, _) ->
  274. Left errMsg
  275. )
  276. )
  277. | Left err -> [| Left err |]
  278. // let tree =
  279. // pathExec ()
  280. // |> Either.bind (fun path ->
  281. // match Qsp.Parser.Main.startOnFile enc path with
  282. // | FParsec.CharParsers.Success(tree, st, _) ->
  283. // Right tree
  284. // | FParsec.CharParsers.Failure(err, st, _) ->
  285. // Left err
  286. // )
  287. // tree
  288. // |> Either.bind parse
  289. // |> sprintf "%A"
  290. // |> printfn "%A"
  291. all ()
  292. |> Seq.map (sprintf "%A")
  293. |> uncurry System.IO.File.WriteAllLines "output.log"
  294. 0