syntax.ml
1:
2:
3:
4: type constant = string
5:
6:
7:
8:
9:
10:
11:
12:
13: type variable = string * int
14:
15:
16: type term =
17: | Var of variable
18: | Const of constant
19: | App of constant * term list
20:
21:
22: type atom = constant * term list
23:
24:
25:
26: type clause = atom list
27:
28:
29: type assertion = atom * clause
30:
31:
32:
33:
34: type environment = (variable * term) list
35:
36:
37:
38: type database = assertion list
39:
40:
41: type toplevel_cmd =
42: | Assert of assertion
43: | Goal of clause
44: | Quit
45: | Use of string
46:
47:
48:
49:
50: let rec lookup env x =
51: try List.assoc x env with Not_found -> Var x
52:
53:
54:
55:
56:
57:
58:
59: let rec subst_term env = function
60: | Var x as e ->
61: (let e' = lookup env x in
62: if e = e' then e' else subst_term env e')
63: | Const _ as e -> e
64: | App (c, ls) -> App (c, List.map (subst_term env) ls)
65:
66:
67:
68: let rec string_of_term = function
69: | Var (v, 0) -> v
70: | Var (v, n) -> v ^ string_of_int n
71: | Const c -> c
72: | App (f, ls) -> f ^ "(" ^ (String.concat ", " (List.map string_of_term ls)) ^ ")"
73:
74:
75:
76:
77: let string_of_env env =
78: match List.filter (fun ((_, n), _) -> n = 0) env with
79: | [] -> "Yes"
80: | env' -> String.concat "\n"
81: (List.map
82: (fun ((x,n), e) ->
83: x ^ " = " ^ string_of_term (subst_term env e))
84: (List.rev env'))
85:
86:
87:
88: let rec occurs x = function
89: Var y -> x = y
90: | Const _ -> false
91: | App (_, ts) -> List.exists (occurs x) ts
unify.ml
1:
2:
3: open Syntax
4:
5:
6: exception NoUnify
7:
8:
9:
10:
11: let rec unify_terms env t1 t2 =
12: match subst_term env t1, subst_term env t2 with
13: | t1, t2 when t1 = t2 -> env
14: | (Var y, t) | (t, Var y) ->
15: if occurs y t then
16: raise NoUnify
17: else
18: (y,t) :: env
19: | Const _, _ -> raise NoUnify
20: | App (c1, ts1), App (c2, ts2) when c1 = c2 -> unify_lists env ts1 ts2
21: | App _, _ -> raise NoUnify
22:
23:
24:
25:
26:
27: and unify_lists env lst1 lst2 =
28: try
29: List.fold_left2 (fun env t1 t2 -> unify_terms env t1 t2) env lst1 lst2
30: with Invalid_argument _ -> raise NoUnify
31:
32:
33:
34:
35: let unify_atoms env (c1,ts1) (c2,ts2) =
36: if c1 = c2 then unify_lists env ts1 ts2 else raise NoUnify
solve.ml
1:
2:
3: open Syntax
4:
5:
6:
7:
8:
9:
10: type choice = database * environment * clause * int
11:
12:
13: let base = ref ([] : database)
14:
15:
16: let assertz a =
17: let rec add = function [] -> [a] | b::bs -> b::(add bs) in
18: (base := add !base)
19:
20:
21: exception NoSolution
22:
23:
24:
25: let rec renumber_term n = function
26: | Var (x,_) -> Var (x,n)
27: | Const _ as c -> c
28: | App (c, ts) -> App (c, List.map (renumber_term n) ts)
29:
30:
31:
32: let rec renumber_atom n (c,ts) = (c, List.map (renumber_term n) ts)
33:
34:
35:
36:
37:
38: let rec display_solution ch env =
39: match string_of_env env, ch with
40: | "Yes", _ -> print_endline "Yes"
41: | answer, [] -> print_endline answer
42: | answer, ch -> begin
43: print_string (answer ^ "\nmore? (y/n) [y] ") ;
44: flush stdout ;
45: match String.lowercase (read_line ()) with
46: | "y" | "yes" | "" -> continue_search ch
47: | _ -> raise NoSolution
48: end
49:
50:
51:
52:
53: and continue_search = function
54: | [] -> raise NoSolution
55: | (asrl,env,gs,n)::cs -> solve cs asrl env gs n
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71: and solve ch asrl env c n =
72:
73:
74:
75:
76:
77: let rec reduce_atom a = function
78: | [] -> None
79: | (b,lst)::asrl' ->
80: (try
81: let env' = Unify.unify_atoms env a (renumber_atom n b) in
82: Some (asrl', env', List.map (renumber_atom n) lst)
83: with Unify.NoUnify -> reduce_atom a asrl')
84: in
85: match c with
86: | [] ->
87:
88: display_solution ch env
89: | a::c' ->
90:
91: (match reduce_atom a asrl with
92: | None ->
93:
94: continue_search ch
95: | Some (asrl', env', d) ->
96:
97:
98:
99: let ch' = (asrl', env, c, n)::ch
100: in
101: solve ch' !base env' (d @ c') (n+1))
102:
103:
104:
105:
106: let solve_toplevel c =
107: try
108: solve [] !base [] c 1
109: with NoSolution -> print_endline "No"
lexer.mll
1: {
2: open Parser
3: open Lexing
4:
5: let incr_linenum lexbuf =
6: let pos = lexbuf.lex_curr_p in
7: lexbuf.lex_curr_p <- { pos with
8: pos_lnum = pos.pos_lnum + 1;
9: pos_bol = pos.pos_cnum;
10: }
11: }
12:
13: let const = ['a'-'z'] ['_' 'a'-'z' 'A'-'Z' '0'-'9']*
14: let var = ['A'-'Z'] ['_' 'a'-'z' 'A'-'Z' '0'-'9']*
15:
16:
17: rule token = parse
18: '#' [^'\n']* '\n' { incr_linenum lexbuf; token lexbuf }
19: | '\n' { incr_linenum lexbuf; token lexbuf }
20: | [' ' '\t'] { token lexbuf }
21: | "$use" { USE }
22: | "$quit" { QUIT }
23: | "?-" { GOAL }
24: | ":-" { FROM }
25: | ";;" { SEMICOLON2 }
26: | "true" { TRUE }
27: | '\"' [^'\"']* '\"' { let str = lexeme lexbuf in
28: STRING (String.sub str 1 (String.length str - 2)) }
29: | '(' { LPAREN }
30: | ')' { RPAREN }
31: | ',' { COMMA }
32: | '.' { PERIOD }
33: | const { CONST (lexeme lexbuf) }
34: | var { VAR (lexeme lexbuf) }
35: | eof { EOF }
36:
37: {
38: }
parser.mly
1: %{
2: open Syntax
3: %}
4:
5: %token <string>VAR
6: %token <string>CONST
7: %token FROM
8: %token COMMA
9: %token TRUE
10: %token PERIOD
11: %token LPAREN RPAREN
12: %token GOAL
13: %token QUIT
14: %token SEMICOLON2
15: %token USE
16: %token <string>STRING
17: %token EOF
18:
19: %start toplevel
20: %type <Syntax.toplevel_cmd list> toplevel
21:
22: %right SEMICOLON2
23: %nonassoc PERIOD GOAL IMPLIED
24: %right COMMA
25:
26: %%
27:
28: toplevel:
29: | EOF { [] }
30: | exprtop { $1 }
31: | cmdtop { $1 }
32:
33: exprtop:
34: | expr EOF { [$1] }
35: | expr toplevel { $1 :: $2 }
36:
37: cmdtop:
38: | cmd EOF { [$1] }
39: | cmd toplevel { $1 :: $2 }
40:
41: cmd:
42: | USE STRING { Use $2 }
43: | QUIT { Quit }
44:
45: expr:
46: | goal { $1 }
47: | assertion { $1 }
48:
49: goal:
50: | GOAL clause PERIOD { Goal $2 }
51:
52: assertion:
53: | atom PERIOD { Assert ($1, []) }
54: | atom FROM clause PERIOD { Assert ($1, $3) }
55:
56: atom:
57: | CONST { ($1, []) }
58: | CONST LPAREN args RPAREN { ($1, $3) }
59:
60: clause:
61: | TRUE { [] }
62: | atom { [$1] }
63: | atom COMMA clause { $1 :: $3 }
64:
65: args:
66: | literal { [$1] }
67: | literal COMMA args { $1 :: $3 }
68:
69: literal:
70: | CONST { Const $1 }
71: | VAR { Var ($1, 0) }
72: | CONST LPAREN args RPAREN { App ($1, $3) }
73:
74:
75: %%
message.ml
1:
2:
3: open Lexing
4:
5:
6:
7:
8:
9: let lexer_from_channel fn ch =
10: let lex = Lexing.from_channel ch in
11: let pos = lex.lex_curr_p in
12: lex.lex_curr_p <- { pos with pos_fname = fn; pos_lnum = 1; } ;
13: lex
14:
15:
16:
17:
18: let lexer_from_string str =
19: let lex = Lexing.from_string str in
20: let pos = lex.lex_curr_p in
21: lex.lex_curr_p <- { pos with pos_fname = ""; pos_lnum = 1; } ;
22: lex
23:
24: let string_of_position {pos_fname=fn; pos_lnum=ln; pos_bol=bol; pos_cnum=cn} =
25: let c = cn - bol in
26: if fn = "" then
27: "Character " ^ string_of_int c
28: else
29: "File \"" ^ fn ^ "\", line " ^ string_of_int ln ^ ", character " ^ string_of_int c
30:
31: let string_of msg pos = string_of_position pos ^ ":\n" ^ msg
32:
33: let syntax_error {lex_curr_p=pos} = string_of "Syntax error" pos
34:
35: let report = print_endline
miniprolog.ml
1:
2:
3: open Message
4: open Syntax
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21: exception Fatal_error of string
22:
23: let fatal_error msg = raise (Fatal_error msg)
24:
25:
26: let rec exec_cmd = function
27: | Goal g -> Solve.solve_toplevel g
28: | Assert a -> Solve.assertz a
29: | Quit -> raise End_of_file
30: | Use fn -> exec_file fn
31:
32:
33: and exec_file fn =
34: let fh = open_in fn in
35: let lex = Message.lexer_from_channel fn fh in
36: try
37: let cmds = Parser.toplevel Lexer.token lex in
38: close_in fh ;
39: exec_cmds cmds
40: with
41: | Sys.Break -> fatal_error "Interrupted."
42: | Parsing.Parse_error | Failure("lexing: empty token") ->
43: fatal_error (Message.syntax_error lex)
44:
45:
46: and exec_cmds cmds = List.iter exec_cmd cmds
47: ;;
48:
49:
50: let shell () =
51: print_string ("Miniprolog. Press ") ;
52: print_string (match Sys.os_type with
53: "Unix" | "Cygwin" -> "Ctrl-D"
54: | "Win32" -> "Ctrl-Z"
55: | _ -> "EOF") ;
56: print_endline " to exit." ;
57: print_endline "Input syntax: " ;
58: print_endline " ?- query. Make a query." ;
59: print_endline " a(t1, ..., tn). Assert an atomic proposition." ;
60: print_endline " A :- B1, ..., Bn. Assert an inference rule." ;
61: print_endline " $quit Exit interpreter." ;
62: print_endline " $use \"filename\" Execute commands from a file." ;
63: try
64: while true do
65: try
66: print_string "Prolog> ";
67: let str = read_line () in
68: let lex = Message.lexer_from_string str in
69: let cmds =
70: try
71: Parser.toplevel Lexer.token lex
72: with
73: | Failure("lexing: empty token")
74: | Parsing.Parse_error -> fatal_error (Message.syntax_error lex)
75: in
76: exec_cmds cmds
77: with
78: | Fatal_error msg -> Message.report msg
79: | Sys.Break -> Message.report ("Interrupted.")
80: done
81: with End_of_file -> print_endline "\nGood bye."
82:
83:
84: let main =
85: Sys.catch_break true ;
86: let noninteractive = ref false in
87: let files = ref [] in
88: Arg.parse
89: [("-n", Arg.Set noninteractive, "do not run the interactive shell")]
90: (fun f -> files := f :: !files)
91: "Usage: miniprolog [-n] [file] ..." ;
92: files := List.rev !files ;
93: try
94: List.iter exec_file !files ;
95: if not !noninteractive then shell ()
96: with Fatal_error msg -> Message.report msg