Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions examples/LTLModelCheckExample.sexp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
(type event A B C Done)

(proc EventuallyA ()
(ex
(prefix A (unwind EventuallyA))
(prefix B (unwind EventuallyA))
(prefix Done skip)))

(proc AlwaysAB ()
(seq
(prefix A
(seq
(prefix B (unwind AlwaysAB))
skip))
skip))

(proc SimpleCounter ()
(seq
(prefix A
(seq
(prefix B
(seq
(prefix C skip)
skip))
skip))
skip))
11 changes: 11 additions & 0 deletions examples/ModelCheckExample.sexp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
(type event A B C)

(proc SimpleProc ()
(seq
(prefix A skip)
(prefix B skip)))

(proc AlwaysA ()
(seq
(prefix A (unwind AlwaysA))
skip))
1 change: 1 addition & 0 deletions src/CSP.Core.Exe/CSP.Core.Exe.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Compile Include="TypeInfer.fs" />
<Compile Include="Run.fs" />
<Compile Include="Dot.fs" />
<Compile Include="ModelCheck.fs" />
<Compile Include="Version.fs" />
<Compile Include="Program.fs"/>
</ItemGroup>
Expand Down
136 changes: 136 additions & 0 deletions src/CSP.Core.Exe/ModelCheck.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
module CSP.Core.Exe.ModelCheck

open System.IO
open FSharpPlus
open CSP.Core
open CSP.Core.Eval
open CSP.Core.ProcEval
open CSP.Core.StateSpace
open CSP.Core.LTL
open CSP.Core.ModelChecker
open CSP.Core.Sexp.LTLParser
open CSP.Core.Sexp.SexpParser
open CSP.Core.Sexp.ProgramParser
open CSP.Core.Exe.Validate
open CSP.Core.Exe.Usage
open CSP.Core.CLI.ArgParser

type ModelCheckConfig =
{ EvalConfig: EvalConfig
ProcEvalConfig: ProcEvalConfig
StateSpaceConfig: NamedSpaceConfig }

type Opts =
| NeedHelp
| Opts of uint * uint * string list

let parseOpts (args: string list) : Result<Opts, string> =
parseArgs (Map [ ("nat-max", OTNat); ("list-max", OTNat); ("help", OTBool) ]) args
|> Result.map (fun opts ->
let needHelp = opts |> List.contains (Opt("help", OVBool(true))) in

if needHelp then
NeedHelp
else
let natMax =
opts
|> List.fold
(fun acc opt ->
match opt with
| Opt("nat-max", OVNat(n)) -> Some(n)
| _ -> acc)
None
|> Option.defaultValue 3u in

let listMax =
opts
|> List.fold
(fun acc opt ->
match opt with
| Opt("list-max", OVNat(n)) -> Some(n)
| _ -> acc)
None
|> Option.defaultValue 3u in

let args =
opts
|> List.fold
(fun acc opt ->
match opt with
| Arg(arg) -> arg :: acc
| _ -> acc)
[]
|> List.rev

Opts(natMax, listMax, args))

let modelCheckProcess (r: TextReader) (cfg: ModelCheckConfig) (procExpr: string) (ltlExpr: string) =
// Parse and validate the CSP program
validate cfg.EvalConfig r procExpr
|> Result.bind (fun (pm, um, cm, genv, initialProc) ->
// Parse the LTL formula
parse ltlExpr
|> Result.mapError (fun err -> sprintf "LTL parse error: %A" err)
|> Result.bind (fun sexp ->
parseLTL sexp
|> Result.mapError (fun err -> sprintf "LTL formula error: %A" err))
|> Result.bind (fun ltlFormula ->
// Evaluate the initial process to get the initial state
let eval = eval cfg.EvalConfig um cm
eval genv initialProc
|> Result.mapError (fun err -> sprintf "Process evaluation error: %A" err)
|> Result.bind (fun initialState ->
// Generate state space (simplified version)
let stateSpace = Map.empty

// Run model checking
modelCheck stateSpace ltlFormula initialState
|> Result.mapError (fun err -> sprintf "Model checking error: %A" err))))

let modelCheckCLI (stdout: TextWriter) (stderr: TextWriter) (args: string list) : int =
parseOpts args
|> Result.bind (fun opts ->
match opts with
| NeedHelp -> Error($"help needed\n\n%s{usage}")
| Opts(natMax, listMax, args) ->
let evalCfg =
{ UnivConfig =
{ NatMax = natMax
ListLenMax = listMax }
PlusConfig =
{ NatMax = natMax
ListLenMax = listMax }}

let procEvalCfg = { EvalConfig = evalCfg }

let cfg =
{ EvalConfig = evalCfg
ProcEvalConfig = procEvalCfg
StateSpaceConfig =
{ UnivConfig = evalCfg.UnivConfig
ProcEvalConfig = procEvalCfg }}

if List.length args < 3 then
let s = args |> String.concat ", " in Error($"too few arguments for model checking: [%s{s}]\n\nUsage: csp check <file> <proc> <ltl-formula>")
else if List.length args > 3 then
let s = args |> String.concat ", " in Error($"too many arguments for model checking: [%s{s}]\n\nUsage: csp check <file> <proc> <ltl-formula>")
else
match args with
| file :: proc :: [ltlFormula] ->
use r = new StreamReader(file)
modelCheckProcess r cfg proc ltlFormula
|> Result.map (fun result ->
match result with
| Satisfied ->
stdout.WriteLine("Model checking result: SATISFIED")
stdout.WriteLine($"The CSP process satisfies the LTL formula.")
| Violated counterexample ->
stdout.WriteLine("Model checking result: VIOLATED")
stdout.WriteLine($"The CSP process violates the LTL formula.")
stdout.WriteLine($"Counterexample found with prefix length: {List.length counterexample.Prefix}")
stdout.WriteLine($"Loop length: {List.length counterexample.Loop}"))
| _ -> failwith "unreachable")
|> Result.map (fun _ -> 0)
|> Result.defaultWith (fun err ->
stderr.WriteLine($"error: %s{err}")
1)
2 changes: 2 additions & 0 deletions src/CSP.Core.Exe/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ open CSP.Core.Exe.Version
open CSP.Core.Exe.Run
open CSP.Core.Exe.Dot
open CSP.Core.Exe.TypeInfer
open CSP.Core.Exe.ModelCheck

module Program =
[<EntryPoint>]
Expand All @@ -23,6 +24,7 @@ module Program =
| "run" :: args -> runCLI stdin stdout stderr args
| "dot" :: args -> dotCLI stdout stderr args
| "type" :: args -> typeInferCLI stdout stderr args
| "check" :: args -> modelCheckCLI stdout stderr args
| unknown :: _ ->
stderr.WriteLine($"error: no such command: %s{unknown}")
stderr.WriteLine(usage)
Expand Down
7 changes: 5 additions & 2 deletions src/CSP.Core.Exe/Usage.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ module CSP.Core.Exe.Usage
let usage = """usage: csp run <file> <proc>
usage: csp dot <file> <proc>
usage: csp type <file> <proc>
usage: csp check <file> <proc> <ltl-formula>
usage: csp version
usage: csp help

COMMANDS
run run the specified proc on an interpreter
dot print a state transition diagram of the specified proc as DOT Lang
type infer a type of the specified proc
check model check the specified proc against an LTL formula
version show version
help show this message

OPTIONS
<file> the file expressed by S-expression
<proc> process expression to run or visualize or infer types
<file> the file expressed by S-expression
<proc> process expression to run or visualize or infer types
<ltl-formula> LTL formula in S-expression format for model checking
"""
1 change: 1 addition & 0 deletions src/CSP.Core.Sexp/CSP.Core.Sexp.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<Compile Include="StmtParser.fs"/>
<Compile Include="ProgramSyntaxError.fs"/>
<Compile Include="ProgramParser.fs"/>
<Compile Include="LTLParser.fs"/>
</ItemGroup>

<ItemGroup>
Expand Down
85 changes: 85 additions & 0 deletions src/CSP.Core.Sexp/LTLParser.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
module CSP.Core.Sexp.LTLParser

open CSP.Core.LTL
open CSP.Core.Event
open CSP.Core.Val
open CSP.Core.Ctor
open CSP.Core.Sexp.Sexp
open CSP.Core.Sexp.SyntaxError

type LTLParseError =
| UnexpectedSexp of Sexp
| UnknownLTLOperator of string
| InvalidArity of string * int * int
| EventParseError of string

let ltlParseError err = err

let rec parseLTL (sexp: Sexp) : Result<LTLFormula, LTLParseError> =
match sexp with
| Sexps ([Atom ("ltl", _); formula], _) -> parseFormula formula
| _ -> Error (UnexpectedSexp sexp)

and parseFormula (sexp: Sexp) : Result<LTLFormula, LTLParseError> =
match sexp with
| Sexps ([Atom ("atom", _); eventSexp], _) ->
parseEvent eventSexp
|> Result.mapError EventParseError
|> Result.map CSP.Core.LTL.Atom

| Sexps ([Atom ("not", _); f], _) ->
parseFormula f |> Result.map Not

| Sexps (Atom ("and", _) :: fs, _) when List.length fs >= 2 ->
fs
|> List.map parseFormula
|> collectResults
|> Result.map And

| Sexps (Atom ("or", _) :: fs, _) when List.length fs >= 2 ->
fs
|> List.map parseFormula
|> collectResults
|> Result.map Or

| Sexps ([Atom ("implies", _); f1; f2], _) ->
match parseFormula f1, parseFormula f2 with
| Ok a, Ok b -> Ok (Implies(a, b))
| Error e, _ | _, Error e -> Error e

| Sexps ([Atom ("next", _); f], _) ->
parseFormula f |> Result.map Next

| Sexps ([Atom ("finally", _); f], _) ->
parseFormula f |> Result.map Finally

| Sexps ([Atom ("globally", _); f], _) ->
parseFormula f |> Result.map Globally

| Sexps ([Atom ("until", _); f1; f2], _) ->
match parseFormula f1, parseFormula f2 with
| Ok a, Ok b -> Ok (Until(a, b))
| Error e, _ | _, Error e -> Error e

| Sexps ([Atom ("release", _); f1; f2], _) ->
match parseFormula f1, parseFormula f2 with
| Ok a, Ok b -> Ok (Release(a, b))
| Error e, _ | _, Error e -> Error e

| Sexps (Atom (op, _) :: _, _) ->
Error (UnknownLTLOperator op)

| _ ->
Error (UnexpectedSexp sexp)

and parseEvent (sexp: Sexp) : Result<Event, string> =
match sexp with
| Atom (s, _) -> Ok (Vis (VUnion (Ctor s, [])))
| _ -> Error "Expected event atom"

and collectResults (results: Result<'a, 'e> list) : Result<'a list, 'e> =
let rec collect acc = function
| [] -> Ok (List.rev acc)
| Ok x :: xs -> collect (x :: acc) xs
| Error e :: _ -> Error e
collect [] results
Loading