summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cjr.sml1
-rw-r--r--src/cjr_env.sml1
-rw-r--r--src/cjr_print.sml20
-rw-r--r--src/cjrize.sml28
-rw-r--r--src/core.sml1
-rw-r--r--src/core_env.sml7
-rw-r--r--src/core_print.sml7
-rw-r--r--src/core_util.sml15
-rw-r--r--src/corify.sml8
-rw-r--r--src/elab.sml1
-rw-r--r--src/elab_env.sml82
-rw-r--r--src/elab_print.sml7
-rw-r--r--src/elab_util.sml14
-rw-r--r--src/elaborate.sml47
-rw-r--r--src/elisp/urweb-mode.el2
-rw-r--r--src/expl.sml1
-rw-r--r--src/expl_env.sml7
-rw-r--r--src/expl_print.sml7
-rw-r--r--src/explify.sml2
-rw-r--r--src/mono.sml1
-rw-r--r--src/mono_env.sml1
-rw-r--r--src/mono_opt.sml25
-rw-r--r--src/mono_print.sml7
-rw-r--r--src/mono_shake.sml2
-rw-r--r--src/mono_util.sml6
-rw-r--r--src/monoize.sml18
-rw-r--r--src/prepare.sml1
-rw-r--r--src/reduce.sml1
-rw-r--r--src/reduce_local.sml1
-rw-r--r--src/shake.sml5
-rw-r--r--src/source.sml1
-rw-r--r--src/source_print.sml7
-rw-r--r--src/unnest.sml1
-rw-r--r--src/urweb.grm13
-rw-r--r--src/urweb.lex1
-rw-r--r--tests/view.ur10
-rw-r--r--tests/view.urp5
-rw-r--r--tests/view.urs1
38 files changed, 325 insertions, 40 deletions
diff --git a/src/cjr.sml b/src/cjr.sml
index 559b7ada..d3fdbc22 100644
--- a/src/cjr.sml
+++ b/src/cjr.sml
@@ -107,6 +107,7 @@ datatype decl' =
| DTable of string * (string * typ) list * string * (string * string) list
| DSequence of string
+ | DView of string * (string * typ) list * string
| DDatabase of {name : string, expunge : int, initialize : int}
| DPreparedStatements of (string * int) list
diff --git a/src/cjr_env.sml b/src/cjr_env.sml
index 7f02a4e9..54dbea17 100644
--- a/src/cjr_env.sml
+++ b/src/cjr_env.sml
@@ -164,6 +164,7 @@ fun declBinds env (d, loc) =
end) env vis
| DTable _ => env
| DSequence _ => env
+ | DView _ => env
| DDatabase _ => env
| DPreparedStatements _ => env
| DJavaScript _ => env
diff --git a/src/cjr_print.sml b/src/cjr_print.sml
index c870c3ed..a09dd7f6 100644
--- a/src/cjr_print.sml
+++ b/src/cjr_print.sml
@@ -2069,6 +2069,15 @@ fun p_decl env (dAll as (d, _) : decl) =
string x,
string " */",
newline]
+ | DView (x, _, s) => box [string "/* SQL view ",
+ string x,
+ space,
+ string "AS",
+ space,
+ string s,
+ space,
+ string " */",
+ newline]
| DDatabase {name, expunge, initialize} =>
box [string "static void uw_db_validate(uw_context);",
newline,
@@ -3089,6 +3098,17 @@ fun p_sql env (ds, _) =
string ";",
newline,
newline]
+ | DView (s, xts, q) =>
+ box [string "CREATE VIEW",
+ space,
+ string s,
+ space,
+ string "AS",
+ space,
+ string q,
+ string ";",
+ newline,
+ newline]
| _ => box []
in
(pp, E.declBinds env dAll)
diff --git a/src/cjrize.sml b/src/cjrize.sml
index ee2ecdb6..19aeee4e 100644
--- a/src/cjrize.sml
+++ b/src/cjrize.sml
@@ -562,6 +562,34 @@ fun cifyDecl ((d, loc), sm) =
end
| L.DSequence s =>
(SOME (L'.DSequence s, loc), NONE, sm)
+ | L.DView (s, xts, e) =>
+ let
+ val (xts, sm) = ListUtil.foldlMap (fn ((x, t), sm) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((x, t), sm)
+ end) sm xts
+
+ fun flatten e =
+ case #1 e of
+ L.ERecord [] => []
+ | L.ERecord [(x, (L.EPrim (Prim.String v), _), _)] => [(x, v)]
+ | L.EStrcat (e1, e2) => flatten e1 @ flatten e2
+ | _ => (ErrorMsg.errorAt loc "Constraint has not been fully determined";
+ Print.prefaces "Undetermined constraint"
+ [("e", MonoPrint.p_exp MonoEnv.empty e)];
+ [])
+
+ val e = case #1 e of
+ L.EPrim (Prim.String s) => s
+ | _ => (ErrorMsg.errorAt loc "VIEW query has not been fully determined";
+ Print.prefaces "Undetermined VIEW query"
+ [("e", MonoPrint.p_exp MonoEnv.empty e)];
+ "")
+ in
+ (SOME (L'.DView (s, xts, e), loc), NONE, sm)
+ end
| L.DDatabase s => (SOME (L'.DDatabase s, loc), NONE, sm)
| L.DJavaScript s => (SOME (L'.DJavaScript s, loc), NONE, sm)
| L.DCookie args => (SOME (L'.DCookie args, loc), NONE, sm)
diff --git a/src/core.sml b/src/core.sml
index 01cf4ec7..131bcc6f 100644
--- a/src/core.sml
+++ b/src/core.sml
@@ -130,6 +130,7 @@ datatype decl' =
| DExport of export_kind * int
| DTable of string * int * con * string * exp * con * exp * con
| DSequence of string * int * string
+ | DView of string * int * string * exp * con
| DDatabase of string
| DCookie of string * int * con * string
| DStyle of string * int * string
diff --git a/src/core_env.sml b/src/core_env.sml
index caf30349..0630fef2 100644
--- a/src/core_env.sml
+++ b/src/core_env.sml
@@ -327,6 +327,13 @@ fun declBinds env (d, loc) =
in
pushENamed env x n t NONE s
end
+ | DView (x, n, s, _, c) =>
+ let
+ val ct = (CFfi ("Basis", "sql_view"), loc)
+ val ct = (CApp (ct, c), loc)
+ in
+ pushENamed env x n ct NONE s
+ end
| DDatabase _ => env
| DCookie (x, n, c, s) =>
let
diff --git a/src/core_print.sml b/src/core_print.sml
index 9c1c72cd..f2a42a7b 100644
--- a/src/core_print.sml
+++ b/src/core_print.sml
@@ -566,6 +566,13 @@ fun p_decl env (dAll as (d, _) : decl) =
string "as",
space,
string s]
+ | DView (x, n, s, e, _) => box [string "view",
+ space,
+ p_named x n,
+ space,
+ string "as",
+ space,
+ p_exp env e]
| DDatabase s => box [string "database",
space,
string s]
diff --git a/src/core_util.sml b/src/core_util.sml
index d05aaa72..ae956121 100644
--- a/src/core_util.sml
+++ b/src/core_util.sml
@@ -946,6 +946,12 @@ fun mapfoldB {kind = fk, con = fc, exp = fe, decl = fd, bind} =
fn cc' =>
(DTable (x, n, c', s, pe', pc', ce', cc'), loc))))))
| DSequence _ => S.return2 dAll
+ | DView (x, n, s, e, c) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (DView (x, n, s, e', c'), loc)))
| DDatabase _ => S.return2 dAll
| DCookie (x, n, c, s) =>
S.map2 (mfc ctx c,
@@ -1082,6 +1088,14 @@ fun mapfoldB (all as {bind, ...}) =
in
bind (ctx, NamedE (x, n, t, NONE, s))
end
+ | DView (x, n, s, _, c) =>
+ let
+ val loc = #2 d'
+ val ct = (CFfi ("Basis", "sql_view"), loc)
+ val ct = (CApp (ct, c), loc)
+ in
+ bind (ctx, NamedE (x, n, ct, NONE, s))
+ end
| DDatabase _ => ctx
| DCookie (x, n, c, s) =>
let
@@ -1154,6 +1168,7 @@ val maxName = foldl (fn ((d, _) : decl, count) =>
| DExport _ => count
| DTable (_, n, _, _, _, _, _, _) => Int.max (n, count)
| DSequence (_, n, _) => Int.max (n, count)
+ | DView (_, n, _, _, _) => Int.max (n, count)
| DDatabase _ => count
| DCookie (_, n, _, _) => Int.max (n, count)
| DStyle (_, n, _) => Int.max (n, count)) 0
diff --git a/src/corify.sml b/src/corify.sml
index c1cd940e..f1895e19 100644
--- a/src/corify.sml
+++ b/src/corify.sml
@@ -992,6 +992,13 @@ fun corifyDecl mods (all as (d, loc : EM.span), st) =
in
([(L'.DSequence (x, n, s), loc)], st)
end
+ | L.DView (_, x, n, e, c) =>
+ let
+ val (st, n) = St.bindVal st x n
+ val s = relify (doRestify (mods, x))
+ in
+ ([(L'.DView (x, n, s, corifyExp st e, corifyCon st c), loc)], st)
+ end
| L.DDatabase s => ([(L'.DDatabase s, loc)], st)
@@ -1063,6 +1070,7 @@ fun maxName ds = foldl (fn ((d, _), n) =>
| L.DExport _ => n
| L.DTable (_, _, n', _, _, _, _, _) => Int.max (n, n')
| L.DSequence (_, _, n') => Int.max (n, n')
+ | L.DView (_, _, n', _, _) => Int.max (n, n')
| L.DDatabase _ => n
| L.DCookie (_, _, n', _) => Int.max (n, n')
| L.DStyle (_, _, n') => Int.max (n, n'))
diff --git a/src/elab.sml b/src/elab.sml
index f82a947d..555cc25c 100644
--- a/src/elab.sml
+++ b/src/elab.sml
@@ -165,6 +165,7 @@ datatype decl' =
| DExport of int * sgn * str
| DTable of int * string * int * con * exp * con * exp * con
| DSequence of int * string * int
+ | DView of int * string * int * exp * con
| DClass of string * int * kind * con
| DDatabase of string
| DCookie of int * string * int * con
diff --git a/src/elab_env.sml b/src/elab_env.sml
index 0184d0b1..efc2b74e 100644
--- a/src/elab_env.sml
+++ b/src/elab_env.sml
@@ -591,6 +591,22 @@ fun unifySubst (rs : con list) =
exception Bad of con * con
+val hasUnif = U.Con.exists {kind = fn _ => false,
+ con = fn CUnif (_, _, _, ref NONE) => true
+ | _ => false}
+
+fun startsWithUnif c =
+ let
+ fun firstArg (c, acc) =
+ case #1 c of
+ CApp (f, x) => firstArg (f, SOME x)
+ | _ => acc
+ in
+ case firstArg (c, NONE) of
+ NONE => false
+ | SOME x => hasUnif x
+ end
+
fun resolveClass (hnorm : con -> con) (consEq : con * con -> bool) (env : env) =
let
fun resolve c =
@@ -671,34 +687,37 @@ fun resolveClass (hnorm : con -> con) (consEq : con * con -> bool) (env : env) =
tryGrounds (#ground class)
end
in
- case #1 c of
- TRecord c =>
- (case #1 (hnorm c) of
- CRecord (_, xts) =>
- let
- fun resolver (xts, acc) =
- case xts of
- [] => SOME (ERecord acc, #2 c)
- | (x, t) :: xts =>
- let
- val t = hnorm t
-
- val t = case t of
- (CApp (f, x), loc) => (CApp (hnorm f, hnorm x), loc)
- | _ => t
- in
- case resolve t of
- NONE => NONE
- | SOME e => resolver (xts, (x, e, t) :: acc)
- end
- in
- resolver (xts, [])
- end
- | _ => NONE)
- | _ =>
- case class_head_in c of
- SOME f => doHead f
- | _ => NONE
+ if startsWithUnif c then
+ NONE
+ else
+ case #1 c of
+ TRecord c =>
+ (case #1 (hnorm c) of
+ CRecord (_, xts) =>
+ let
+ fun resolver (xts, acc) =
+ case xts of
+ [] => SOME (ERecord acc, #2 c)
+ | (x, t) :: xts =>
+ let
+ val t = hnorm t
+
+ val t = case t of
+ (CApp (f, x), loc) => (CApp (hnorm f, hnorm x), loc)
+ | _ => t
+ in
+ case resolve t of
+ NONE => NONE
+ | SOME e => resolver (xts, (x, e, t) :: acc)
+ end
+ in
+ resolver (xts, [])
+ end
+ | _ => NONE)
+ | _ =>
+ case class_head_in c of
+ SOME f => doHead f
+ | _ => NONE
end
in
resolve
@@ -1482,6 +1501,13 @@ fun declBinds env (d, loc) =
in
pushENamedAs env x n t
end
+ | DView (tn, x, n, _, c) =>
+ let
+ val ct = (CModProj (tn, [], "sql_view"), loc)
+ val ct = (CApp (ct, c), loc)
+ in
+ pushENamedAs env x n ct
+ end
| DClass (x, n, k, c) =>
let
val k = (KArrow (k, (KType, loc)), loc)
diff --git a/src/elab_print.sml b/src/elab_print.sml
index e6a2cccb..bbbd9f8d 100644
--- a/src/elab_print.sml
+++ b/src/elab_print.sml
@@ -758,6 +758,13 @@ fun p_decl env (dAll as (d, _) : decl) =
| DSequence (_, x, n) => box [string "sequence",
space,
p_named x n]
+ | DView (_, x, n, e, _) => box [string "view",
+ space,
+ p_named x n,
+ space,
+ string "as",
+ space,
+ p_exp env e]
| DClass (x, n, k, c) => box [string "class",
space,
p_named x n,
diff --git a/src/elab_util.sml b/src/elab_util.sml
index 0d78951b..f4cbc951 100644
--- a/src/elab_util.sml
+++ b/src/elab_util.sml
@@ -791,6 +791,13 @@ fun mapfoldB {kind = fk, con = fc, exp = fe, sgn_item = fsgi, sgn = fsg, str = f
end
| DSequence (tn, x, n) =>
bind (ctx, NamedE (x, (CModProj (n, [], "sql_sequence"), loc)))
+ | DView (tn, x, n, _, c) =>
+ let
+ val ct = (CModProj (n, [], "sql_view"), loc)
+ val ct = (CApp (ct, c), loc)
+ in
+ bind (ctx, NamedE (x, ct))
+ end
| DClass (x, n, k, _) =>
bind (ctx, NamedC (x, n, (KArrow (k, (KType, loc)), loc)))
| DDatabase _ => ctx
@@ -899,6 +906,12 @@ fun mapfoldB {kind = fk, con = fc, exp = fe, sgn_item = fsgi, sgn = fsg, str = f
fn cc' =>
(DTable (tn, x, n, c', pe', pc', ce', cc'), loc))))))
| DSequence _ => S.return2 dAll
+ | DView (tn, x, n, e, c) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (DView (tn, x, n, e', c'), loc)))
| DClass (x, n, k, c) =>
S.bind2 (mfk ctx k,
@@ -1051,6 +1064,7 @@ and maxNameDecl (d, _) =
| DExport _ => 0
| DTable (n1, _, n2, _, _, _, _, _) => Int.max (n1, n2)
| DSequence (n1, _, n2) => Int.max (n1, n2)
+ | DView (n1, _, n2, _, _) => Int.max (n1, n2)
| DDatabase _ => 0
| DCookie (n1, _, n2, _) => Int.max (n1, n2)
| DStyle (n1, _, n2) => Int.max (n1, n2)
diff --git a/src/elaborate.sml b/src/elaborate.sml
index 81fcbda1..b9378e1b 100644
--- a/src/elaborate.sml
+++ b/src/elaborate.sml
@@ -803,19 +803,22 @@
handle GuessFailure => false
end
- val (fs1, fs2, others1, others2) =
+ val (fs1, fs2, others1, others2, unifs1, unifs2) =
case (fs1, fs2, others1, others2, unifs1, unifs2) of
([], _, [other1], [], [], _) =>
if isGuessable (other1, fs2, unifs2) then
- ([], [], [], [])
+ ([], [], [], [], [], [])
else
- (fs1, fs2, others1, others2)
+ (fs1, fs2, others1, others2, unifs1, unifs2)
| (_, [], [], [other2], _, []) =>
if isGuessable (other2, fs1, unifs1) then
- ([], [], [], [])
+ ([], [], [], [], [], [])
else
- (fs1, fs2, others1, others2)
- | _ => (fs1, fs2, others1, others2)
+ (prefaces "Not guessable" [("other2", p_con env other2),
+ ("fs1", p_con env (L'.CRecord (k, fs1), loc)),
+ ("#unifs1", PD.string (Int.toString (length unifs1)))];
+ (fs1, fs2, others1, others2, unifs1, unifs2))
+ | _ => (fs1, fs2, others1, others2, unifs1, unifs2)
(*val () = eprefaces "Summaries5" [("#1", p_summary env {fields = fs1, unifs = unifs1, others = others1}),
("#2", p_summary env {fields = fs2, unifs = unifs2, others = others2})]*)
@@ -849,7 +852,7 @@
fun unfold (dom, ran, f, r, c) =
let
fun unfold (r, c) =
- case #1 c of
+ case #1 (hnormCon env c) of
L'.CRecord (_, []) => unifyCons env r (L'.CRecord (dom, []), loc)
| L'.CRecord (_, [(x, v)]) =>
let
@@ -878,8 +881,7 @@
unfold (r2, c2');
unifyCons env r (L'.CConcat (r1, r2), loc)
end
- | L'.CUnif (_, _, _, ref (SOME c)) => unfold (r, c)
- | L'.CUnif (_, _, _, ur as ref NONE) =>
+ | L'.CUnif (_, _, _, ur) =>
let
val ur' = cunif (loc, (L'.KRecord dom, loc))
in
@@ -1935,6 +1937,8 @@ val hnormSgn = E.hnormSgn
fun tableOf () = (L'.CModProj (!basis_r, [], "sql_table"), ErrorMsg.dummySpan)
fun sequenceOf () = (L'.CModProj (!basis_r, [], "sql_sequence"), ErrorMsg.dummySpan)
+fun viewOf () = (L'.CModProj (!basis_r, [], "sql_view"), ErrorMsg.dummySpan)
+fun queryOf () = (L'.CModProj (!basis_r, [], "sql_query"), ErrorMsg.dummySpan)
fun cookieOf () = (L'.CModProj (!basis_r, [], "http_cookie"), ErrorMsg.dummySpan)
fun styleOf () = (L'.CModProj (!basis_r, [], "css_class"), ErrorMsg.dummySpan)
@@ -2434,6 +2438,8 @@ and sgiOfDecl (d, loc) =
[(L'.SgiVal (x, n, (L'.CApp ((L'.CApp (tableOf (), c), loc),
(L'.CConcat (pc, cc), loc)), loc)), loc)]
| L'.DSequence (tn, x, n) => [(L'.SgiVal (x, n, sequenceOf ()), loc)]
+ | L'.DView (tn, x, n, _, c) =>
+ [(L'.SgiVal (x, n, (L'.CApp (viewOf (), c), loc)), loc)]
| L'.DClass (x, n, k, c) => [(L'.SgiClass (x, n, k, c), loc)]
| L'.DDatabase _ => []
| L'.DCookie (tn, x, n, c) => [(L'.SgiVal (x, n, (L'.CApp (cookieOf (), c), loc)), loc)]
@@ -3405,6 +3411,29 @@ and elabDecl (dAll as (d, loc), (env, denv, gs)) =
in
([(L'.DSequence (!basis_r, x, n), loc)], (env, denv, gs))
end
+ | L.DView (x, e) =>
+ let
+ val (e', t, gs') = elabExp (env, denv) e
+
+ val k = (L'.KRecord (L'.KType, loc), loc)
+ val fs = cunif (loc, k)
+ val ts = cunif (loc, (L'.KRecord k, loc))
+ val tf = (L'.CApp ((L'.CMap (k, k), loc),
+ (L'.CAbs ("_", k, (L'.CRecord ((L'.KType, loc), []), loc)), loc)), loc)
+ val ts = (L'.CApp (tf, ts), loc)
+
+ val cv = viewOf ()
+ val cv = (L'.CApp (cv, fs), loc)
+ val (env', n) = E.pushENamed env x cv
+
+ val ct = queryOf ()
+ val ct = (L'.CApp (ct, ts), loc)
+ val ct = (L'.CApp (ct, fs), loc)
+ in
+ checkCon env e' t ct;
+ ([(L'.DView (!basis_r, x, n, e', fs), loc)],
+ (env', denv, gs' @ gs))
+ end
| L.DClass (x, k, c) =>
let
diff --git a/src/elisp/urweb-mode.el b/src/elisp/urweb-mode.el
index 2cd27fcc..7f4b0dee 100644
--- a/src/elisp/urweb-mode.el
+++ b/src/elisp/urweb-mode.el
@@ -137,7 +137,7 @@ See doc for the variable `urweb-mode-info'."
"fun" "functor" "if" "include"
"of" "open" "let" "in"
"rec" "sequence" "sig" "signature" "cookie" "style"
- "struct" "structure" "table" "then" "type" "val" "where"
+ "struct" "structure" "table" "view" "then" "type" "val" "where"
"with"
"Name" "Type" "Unit")
diff --git a/src/expl.sml b/src/expl.sml
index e293c36b..cc40e8b4 100644
--- a/src/expl.sml
+++ b/src/expl.sml
@@ -143,6 +143,7 @@ datatype decl' =
| DExport of int * sgn * str
| DTable of int * string * int * con * exp * con * exp * con
| DSequence of int * string * int
+ | DView of int * string * int * exp * con
| DDatabase of string
| DCookie of int * string * int * con
| DStyle of int * string * int
diff --git a/src/expl_env.sml b/src/expl_env.sml
index 1e99b36b..2bb049a3 100644
--- a/src/expl_env.sml
+++ b/src/expl_env.sml
@@ -312,6 +312,13 @@ fun declBinds env (d, loc) =
in
pushENamed env x n t
end
+ | DView (tn, x, n, _, c) =>
+ let
+ val ct = (CModProj (tn, [], "sql_view"), loc)
+ val ct = (CApp (ct, c), loc)
+ in
+ pushENamed env x n ct
+ end
| DDatabase _ => env
| DCookie (tn, x, n, c) =>
let
diff --git a/src/expl_print.sml b/src/expl_print.sml
index 167c6850..e6b28fea 100644
--- a/src/expl_print.sml
+++ b/src/expl_print.sml
@@ -681,6 +681,13 @@ fun p_decl env (dAll as (d, _) : decl) =
| DSequence (_, x, n) => box [string "sequence",
space,
p_named x n]
+ | DView (_, x, n, e, _) => box [string "view",
+ space,
+ p_named x n,
+ space,
+ string "as",
+ space,
+ p_exp env e]
| DDatabase s => box [string "database",
space,
string s]
diff --git a/src/explify.sml b/src/explify.sml
index 6a33eabc..2e181771 100644
--- a/src/explify.sml
+++ b/src/explify.sml
@@ -182,6 +182,8 @@ fun explifyDecl (d, loc : EM.span) =
SOME (L'.DTable (nt, x, n, explifyCon c,
explifyExp pe, explifyCon pc,
explifyExp ce, explifyCon cc), loc)
+ | L.DView (nt, x, n, e, c) =>
+ SOME (L'.DView (nt, x, n, explifyExp e, explifyCon c), loc)
| L.DSequence (nt, x, n) => SOME (L'.DSequence (nt, x, n), loc)
| L.DClass (x, n, k, c) => SOME (L'.DCon (x, n,
(L'.KArrow (explifyKind k, (L'.KType, loc)), loc), explifyCon c), loc)
diff --git a/src/mono.sml b/src/mono.sml
index e9d30181..7a789e2c 100644
--- a/src/mono.sml
+++ b/src/mono.sml
@@ -127,6 +127,7 @@ datatype decl' =
| DTable of string * (string * typ) list * exp * exp
| DSequence of string
+ | DView of string * (string * typ) list * exp
| DDatabase of {name : string, expunge : int, initialize : int}
| DJavaScript of string
diff --git a/src/mono_env.sml b/src/mono_env.sml
index b3572fbe..739f2f89 100644
--- a/src/mono_env.sml
+++ b/src/mono_env.sml
@@ -109,6 +109,7 @@ fun declBinds env (d, loc) =
| DExport _ => env
| DTable _ => env
| DSequence _ => env
+ | DView _ => env
| DDatabase _ => env
| DJavaScript _ => env
| DCookie _ => env
diff --git a/src/mono_opt.sml b/src/mono_opt.sml
index 19244e60..41724eb0 100644
--- a/src/mono_opt.sml
+++ b/src/mono_opt.sml
@@ -422,6 +422,31 @@ fun exp e =
EPrim (Prim.String s)
end
+ | EFfiApp ("Basis", "viewify", [(EPrim (Prim.String s), loc)]) =>
+ let
+ fun uwify (cs, acc) =
+ case cs of
+ [] => String.concat (rev acc)
+ | #"A" :: #"S" :: #" " :: #"_" :: cs => uwify (cs, "AS uw_" :: acc)
+ | #"'" :: cs =>
+ let
+ fun waitItOut (cs, acc) =
+ case cs of
+ [] => raise Fail "MonoOpt: Unterminated SQL string literal"
+ | #"'" :: cs => uwify (cs, "'" :: acc)
+ | #"\\" :: #"'" :: cs => waitItOut (cs, "\\'" :: acc)
+ | #"\\" :: #"\\" :: cs => waitItOut (cs, "\\\\" :: acc)
+ | c :: cs => waitItOut (cs, str c :: acc)
+ in
+ waitItOut (cs, "'" :: acc)
+ end
+ | c :: cs => uwify (cs, str c :: acc)
+
+ val s = uwify (String.explode s, [])
+ in
+ EPrim (Prim.String s)
+ end
+
| _ => e
and optExp e = #1 (U.Exp.map {typ = typ, exp = exp} e)
diff --git a/src/mono_print.sml b/src/mono_print.sml
index ffc1d4fe..a233b400 100644
--- a/src/mono_print.sml
+++ b/src/mono_print.sml
@@ -438,6 +438,13 @@ fun p_decl env (dAll as (d, _) : decl) =
| DSequence s => box [string "(* SQL sequence ",
string s,
string "*)"]
+ | DView (s, _, e) => box [string "(* SQL view ",
+ string s,
+ space,
+ string "as",
+ space,
+ p_exp env e,
+ string "*)"]
| DDatabase {name, expunge, initialize} => box [string "database",
space,
string name,
diff --git a/src/mono_shake.sml b/src/mono_shake.sml
index 0060d036..4764feb7 100644
--- a/src/mono_shake.sml
+++ b/src/mono_shake.sml
@@ -57,6 +57,7 @@ fun shake file =
| ((DExport _, _), acc) => acc
| ((DTable _, _), acc) => acc
| ((DSequence _, _), acc) => acc
+ | ((DView _, _), acc) => acc
| ((DDatabase _, _), acc) => acc
| ((DJavaScript _, _), acc) => acc
| ((DCookie _, _), acc) => acc
@@ -116,6 +117,7 @@ fun shake file =
| (DExport _, _) => true
| (DTable _, _) => true
| (DSequence _, _) => true
+ | (DView _, _) => true
| (DDatabase _, _) => true
| (DJavaScript _, _) => true
| (DCookie _, _) => true
diff --git a/src/mono_util.sml b/src/mono_util.sml
index dd848ba6..caf96ac7 100644
--- a/src/mono_util.sml
+++ b/src/mono_util.sml
@@ -492,6 +492,10 @@ fun mapfoldB {typ = fc, exp = fe, decl = fd, bind} =
fn ce' =>
(DTable (s, xts, pe', ce'), loc)))
| DSequence _ => S.return2 dAll
+ | DView (s, xts, e) =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (DView (s, xts, e'), loc))
| DDatabase _ => S.return2 dAll
| DJavaScript _ => S.return2 dAll
| DCookie _ => S.return2 dAll
@@ -575,6 +579,7 @@ fun mapfoldB (all as {bind, ...}) =
| DExport _ => ctx
| DTable _ => ctx
| DSequence _ => ctx
+ | DView _ => ctx
| DDatabase _ => ctx
| DJavaScript _ => ctx
| DCookie _ => ctx
@@ -626,6 +631,7 @@ val maxName = foldl (fn ((d, _) : decl, count) =>
| DExport _ => count
| DTable _ => count
| DSequence _ => count
+ | DView _ => count
| DDatabase _ => count
| DJavaScript _ => count
| DCookie _ => count
diff --git a/src/monoize.sml b/src/monoize.sml
index ccc5a851..a2048a7d 100644
--- a/src/monoize.sml
+++ b/src/monoize.sml
@@ -2938,6 +2938,24 @@ fun monoDecl (env, fm) (all as (d, loc)) =
(L'.DVal (x, n, t', e_name, s), loc)])
end
| L.DTable _ => poly ()
+ | L.DView (x, n, s, e, (L.CRecord (_, xts), _)) =>
+ let
+ val t = (L.CFfi ("Basis", "string"), loc)
+ val t' = (L'.TFfi ("Basis", "string"), loc)
+ val s = "uw_" ^ s
+ val e_name = (L'.EPrim (Prim.String s), loc)
+
+ val xts = map (fn (x, t) => (monoName env x, monoType env t)) xts
+
+ val (e, fm) = monoExp (env, St.empty, fm) e
+ val e = (L'.EFfiApp ("Basis", "viewify", [e]), loc)
+ in
+ SOME (Env.pushENamed env x n t NONE s,
+ fm,
+ [(L'.DView (s, xts, e), loc),
+ (L'.DVal (x, n, t', e_name, s), loc)])
+ end
+ | L.DView _ => poly ()
| L.DSequence (x, n, s) =>
let
val t = (L.CFfi ("Basis", "string"), loc)
diff --git a/src/prepare.sml b/src/prepare.sml
index 25306e89..592b00bc 100644
--- a/src/prepare.sml
+++ b/src/prepare.sml
@@ -266,6 +266,7 @@ fun prepDecl (d as (_, loc), sns) =
| DTable _ => (d, sns)
| DSequence _ => (d, sns)
+ | DView _ => (d, sns)
| DDatabase _ => (d, sns)
| DPreparedStatements _ => (d, sns)
| DJavaScript _ => (d, sns)
diff --git a/src/reduce.sml b/src/reduce.sml
index 914f26c0..665c10b4 100644
--- a/src/reduce.sml
+++ b/src/reduce.sml
@@ -467,6 +467,7 @@ fun reduce file =
exp (namedC, namedE) [] ce,
con namedC [] cc), loc), st)
| DSequence _ => (d, st)
+ | DView (s, n, s', e, c) => ((DView (s, n, s', exp (namedC, namedE) [] e, con namedC [] c), loc), st)
| DDatabase _ => (d, st)
| DCookie (s, n, c, s') => ((DCookie (s, n, con namedC [] c, s'), loc), st)
| DStyle (s, n, s') => ((DStyle (s, n, s'), loc), st)
diff --git a/src/reduce_local.sml b/src/reduce_local.sml
index 265cb2a4..6c25ebf3 100644
--- a/src/reduce_local.sml
+++ b/src/reduce_local.sml
@@ -158,6 +158,7 @@ fun reduce file =
| DExport _ => d
| DTable _ => d
| DSequence _ => d
+ | DView _ => d
| DDatabase _ => d
| DCookie _ => d
| DStyle _ => d
diff --git a/src/shake.sml b/src/shake.sml
index 787bfd2f..35af7436 100644
--- a/src/shake.sml
+++ b/src/shake.sml
@@ -84,6 +84,8 @@ fun shake file =
(cdef, IM.insert (edef, n, ([], c, dummye)))
| ((DSequence (_, n, _), _), (cdef, edef)) =>
(cdef, IM.insert (edef, n, ([], dummyt, dummye)))
+ | ((DView (_, n, _, _, c), _), (cdef, edef)) =>
+ (cdef, IM.insert (edef, n, ([], c, dummye)))
| ((DDatabase _, _), acc) => acc
| ((DCookie (_, n, c, _), _), (cdef, edef)) =>
(cdef, IM.insert (edef, n, ([], c, dummye)))
@@ -159,8 +161,9 @@ fun shake file =
| (DVal (_, n, _, _, _), _) => IS.member (#exp s, n)
| (DValRec vis, _) => List.exists (fn (_, n, _, _, _) => IS.member (#exp s, n)) vis
| (DExport _, _) => true
- | (DTable _, _) => true
+ | (DView _, _) => true
| (DSequence _, _) => true
+ | (DTable _, _) => true
| (DDatabase _, _) => true
| (DCookie _, _) => true
| (DStyle _, _) => true) file
diff --git a/src/source.sml b/src/source.sml
index 6645ae75..9d3eea79 100644
--- a/src/source.sml
+++ b/src/source.sml
@@ -161,6 +161,7 @@ datatype decl' =
| DExport of str
| DTable of string * con * exp * exp
| DSequence of string
+ | DView of string * exp
| DClass of string * kind * con
| DDatabase of string
| DCookie of string * con
diff --git a/src/source_print.sml b/src/source_print.sml
index 58867f64..0f8b093b 100644
--- a/src/source_print.sml
+++ b/src/source_print.sml
@@ -621,6 +621,13 @@ fun p_decl ((d, _) : decl) =
| DSequence x => box [string "sequence",
space,
string x]
+ | DView (x, e) => box [string "view",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ p_exp e]
| DClass (x, k, c) => box [string "class",
space,
string x,
diff --git a/src/unnest.sml b/src/unnest.sml
index c321b34d..51b66aa4 100644
--- a/src/unnest.sml
+++ b/src/unnest.sml
@@ -404,6 +404,7 @@ fun unnest file =
| DExport _ => default ()
| DTable _ => default ()
| DSequence _ => default ()
+ | DView _ => default ()
| DClass _ => default ()
| DDatabase _ => default ()
| DCookie _ => default ()
diff --git a/src/urweb.grm b/src/urweb.grm
index ce078279..da817ab3 100644
--- a/src/urweb.grm
+++ b/src/urweb.grm
@@ -195,7 +195,7 @@ datatype attr = Class of exp | Normal of con * exp
| FN | PLUSPLUS | MINUSMINUS | MINUSMINUSMINUS | DOLLAR | TWIDDLE | CARET
| LET | IN
| STRUCTURE | SIGNATURE | STRUCT | SIG | END | FUNCTOR | WHERE | EXTERN | SQL
- | INCLUDE | OPEN | CONSTRAINT | CONSTRAINTS | EXPORT | TABLE | SEQUENCE
+ | INCLUDE | OPEN | CONSTRAINT | CONSTRAINTS | EXPORT | TABLE | SEQUENCE | VIEW
| COOKIE | STYLE
| CASE | IF | THEN | ELSE
@@ -438,6 +438,10 @@ decl : CON SYMBOL cargl2 kopt EQ cexp (let
| TABLE SYMBOL COLON cterm pkopt commaOpt cstopt([(DTable (SYMBOL, entable cterm, pkopt, cstopt),
s (TABLEleft, cstoptright))])
| SEQUENCE SYMBOL ([(DSequence SYMBOL, s (SEQUENCEleft, SYMBOLright))])
+ | VIEW SYMBOL EQ query ([(DView (SYMBOL, query),
+ s (VIEWleft, queryright))])
+ | VIEW SYMBOL EQ LBRACE eexp RBRACE ([(DView (SYMBOL, eexp),
+ s (VIEWleft, RBRACEright))])
| CLASS SYMBOL EQ cexp (let
val loc = s (CLASSleft, cexpright)
in
@@ -674,6 +678,13 @@ sgi : CON SYMBOL DCOLON kind ((SgiConAbs (SYMBOL, kind), s (CONleft,
in
(SgiVal (SYMBOL, t), loc)
end)
+ | VIEW SYMBOL COLON cexp (let
+ val loc = s (VIEWleft, cexpright)
+ val t = (CVar (["Basis"], "sql_view"), loc)
+ val t = (CApp (t, cexp), loc)
+ in
+ (SgiVal (SYMBOL, t), loc)
+ end)
| CLASS SYMBOL (let
val loc = s (CLASSleft, SYMBOLright)
val k = (KArrow ((KType, loc), (KType, loc)), loc)
diff --git a/src/urweb.lex b/src/urweb.lex
index bb9004a6..85cf3bcf 100644
--- a/src/urweb.lex
+++ b/src/urweb.lex
@@ -317,6 +317,7 @@ notags = [^<{\n]+;
<INITIAL> "export" => (Tokens.EXPORT (pos yypos, pos yypos + size yytext));
<INITIAL> "table" => (Tokens.TABLE (pos yypos, pos yypos + size yytext));
<INITIAL> "sequence" => (Tokens.SEQUENCE (pos yypos, pos yypos + size yytext));
+<INITIAL> "view" => (Tokens.VIEW (pos yypos, pos yypos + size yytext));
<INITIAL> "class" => (Tokens.CLASS (pos yypos, pos yypos + size yytext));
<INITIAL> "cookie" => (Tokens.COOKIE (pos yypos, pos yypos + size yytext));
<INITIAL> "style" => (Tokens.STYLE (pos yypos, pos yypos + size yytext));
diff --git a/tests/view.ur b/tests/view.ur
new file mode 100644
index 00000000..36d77deb
--- /dev/null
+++ b/tests/view.ur
@@ -0,0 +1,10 @@
+table t : { A : int, B : string }
+
+view v = SELECT t.A AS X FROM t
+
+fun main () =
+ rows <- queryX (SELECT * FROM v)
+ (fn r => <xml><li>{[r.V.X]}</li></xml>);
+ return <xml><body>
+ {rows}
+ </body></xml>
diff --git a/tests/view.urp b/tests/view.urp
new file mode 100644
index 00000000..3528ec9d
--- /dev/null
+++ b/tests/view.urp
@@ -0,0 +1,5 @@
+debug
+database dbname=view
+sql view.sql
+
+view
diff --git a/tests/view.urs b/tests/view.urs
new file mode 100644
index 00000000..6ac44e0b
--- /dev/null
+++ b/tests/view.urs
@@ -0,0 +1 @@
+val main : unit -> transaction page