con rawMeta = fn t :: Type =>
{New : transaction t,
Inj : sql_injectable t}
con colMeta' = fn (row :: {Type}) (input :: Type) (filter :: Type) =>
{Header : string,
Project : $row -> transaction input,
Update : $row -> input -> transaction ($row),
Display : input -> xbody,
Edit : input -> xbody,
Validate : input -> signal bool,
CreateFilter : transaction filter,
DisplayFilter : filter -> xbody,
Filter : filter -> $row -> signal bool}
con colMeta = fn (row :: {Type}) (global_input_filter :: (Type * Type * Type)) =>
{Initialize : transaction global_input_filter.1,
Handlers : global_input_filter.1 -> colMeta' row global_input_filter.2 global_input_filter.3}
con aggregateMeta = fn (row :: {Type}) (acc :: Type) =>
{Initial : acc,
Step : $row -> acc -> acc,
Display : acc -> xbody}
structure Direct = struct
con metaBase = fn actual_input_filter :: (Type * Type * Type) =>
{Display : actual_input_filter.2 -> xbody,
Edit : actual_input_filter.2 -> xbody,
Initialize : actual_input_filter.1 -> transaction actual_input_filter.2,
Parse : actual_input_filter.2 -> signal (option actual_input_filter.1),
CreateFilter : transaction actual_input_filter.3,
DisplayFilter : actual_input_filter.3 -> xbody,
Filter : actual_input_filter.3 -> actual_input_filter.1 -> signal bool}
datatype metaBoth actual input filter =
NonNull of metaBase (actual, input, filter) * metaBase (option actual, input, filter)
| Nullable of metaBase (actual, input, filter)
con meta = fn global_actual_input_filter :: (Type * Type * Type * Type) =>
{Initialize : transaction global_actual_input_filter.1,
Handlers : global_actual_input_filter.1
-> metaBoth global_actual_input_filter.2 global_actual_input_filter.3
global_actual_input_filter.4}
con editableState (ts :: (Type * Type * Type * Type)) = (ts.1, ts.3, ts.4)
fun editable [ts] [rest] [nm :: Name] [[nm] ~ rest] name (m : meta ts) : colMeta ([nm = ts.2] ++ rest)
(editableState ts) =
let
fun doMr mr = {Header = name,
Project = fn r => mr.Initialize r.nm,
Update = fn r s =>
vo <- current (mr.Parse s);
return (case vo of
None => r
| Some v => r -- nm ++ {nm = v}),
Display = mr.Display,
Edit = mr.Edit,
Validate = fn s => vo <- mr.Parse s; return (Option.isSome vo),
CreateFilter = mr.CreateFilter,
DisplayFilter = mr.DisplayFilter,
Filter = fn i r => mr.Filter i r.nm}
in
{Initialize = m.Initialize,
Handlers = fn data => case m.Handlers data of
NonNull (mr, _) => doMr mr
| Nullable mr => doMr mr}
end
con readOnlyState (ts :: (Type * Type * Type * Type)) = (ts.1, ts.3, ts.4)
fun readOnly [ts] [rest] [nm :: Name] [[nm] ~ rest] name (m : meta ts) : colMeta ([nm = ts.2] ++ rest)
(readOnlyState ts) =
let
fun doMr mr = {Header = name,
Project = fn r => mr.Initialize r.nm,
Update = fn r _ => return r,
Display = mr.Display,
Edit = mr.Display,
Validate = fn _ => return True,
CreateFilter = mr.CreateFilter,
DisplayFilter = mr.DisplayFilter,
Filter = fn i r => mr.Filter i r.nm}
in
{Initialize = m.Initialize,
Handlers = fn data => case m.Handlers data of
NonNull (mr, _) => doMr mr
| Nullable mr => doMr mr}
end
con metaBasic = fn actual_input_filter :: (Type * Type * Type) =>
{Display : actual_input_filter.2 -> xbody,
Edit : source actual_input_filter.2 -> xbody,
Initialize : actual_input_filter.1 -> actual_input_filter.2,
InitializeNull : actual_input_filter.2,
IsNull : actual_input_filter.2 -> bool,
Parse : actual_input_filter.2 -> option actual_input_filter.1,
CreateFilter : actual_input_filter.3,
DisplayFilter : source actual_input_filter.3 -> xbody,
Filter : actual_input_filter.3 -> actual_input_filter.1 -> bool,
FilterIsNull : actual_input_filter.3 -> bool}
con basicState = source
con basicFilter = source
fun basic [ts ::: (Type * Type * Type)] (m : metaBasic ts) : meta (unit, ts.1, basicState ts.2, basicFilter ts.3) =
{Initialize = return (),
Handlers = fn () => NonNull (
{Display = fn s => ,
Edit = m.Edit,
Initialize = fn v => source (m.Initialize v),
Parse = fn s => v <- signal s; return (m.Parse v),
CreateFilter = source m.CreateFilter,
DisplayFilter = m.DisplayFilter,
Filter = fn f v => f <- signal f;
return (if m.FilterIsNull f then
True
else
m.Filter f v)},
{Display = fn s => ,
Edit = m.Edit,
Initialize = fn v => source (case v of
None => m.InitializeNull
| Some v => m.Initialize v),
Parse = fn s => v <- signal s;
return (if m.IsNull v then
Some None
else
case m.Parse v of
None => None
| Some v' => Some (Some v')),
CreateFilter = source m.CreateFilter,
DisplayFilter = m.DisplayFilter,
Filter = fn f v => f <- signal f;
return (if m.FilterIsNull f then
True
else
case v of
None => False
| Some v => m.Filter f v) : signal bool})}
fun nullable [global] [actual] [input] [filter] (m : meta (global, actual, input, filter)) =
{Initialize = m.Initialize,
Handlers = fn d => case m.Handlers d of
Nullable _ => error Don't stack calls to Direct.nullable!
| NonNull (_, ho) => Nullable ho}
type intGlobal = unit
type intInput = basicState string
type intFilter = basicFilter string
val int : meta (intGlobal, int, intInput, intFilter) =
basic {Display = fn s => {[s]},
Edit = fn s => ,
Initialize = fn n => show n,
InitializeNull = "",
IsNull = eq "",
Parse = fn v => read v,
CreateFilter = "",
DisplayFilter = fn s => : xbody,
Filter = fn s n =>
case read s of
None => True
| Some n' => n' = n,
FilterIsNull = eq ""}
type stringGlobal = unit
type stringInput = basicState string
type stringFilter = basicFilter string
val string : meta (stringGlobal, string, stringInput, stringFilter) =
basic {Display = fn s => {[s]},
Edit = fn s => ,
Initialize = fn s => s,
InitializeNull = "",
IsNull = eq "",
Parse = fn s => Some s,
CreateFilter = "",
DisplayFilter = fn s => : xbody,
Filter = fn s n =>
case read s of
None => True
| Some n' => n' = n,
FilterIsNull = eq ""}
type boolGlobal = unit
type boolInput = basicState bool
type boolFilter = basicFilter string
val bool : meta (boolGlobal, bool, boolInput, boolFilter) =
basic {Display = fn b => {[b]},
Edit = fn s => ,
Initialize = fn b => b,
InitializeNull = False,
IsNull = fn _ => False,
Parse = fn b => Some b,
CreateFilter = "",
DisplayFilter = fn s =>
False
True
: xbody,
Filter = fn s b =>
case s of
"0" => b = False
| "1" => b = True
| _ => True,
FilterIsNull = eq ""}
functor Foreign (M : sig
con row :: {Type}
con t :: Type
val show_t : show t
val read_t : read t
val eq_t : eq t
val inj_t : sql_injectable t
con nm :: Name
constraint [nm] ~ row
table tab : ([nm = t] ++ row)
val render : $([nm = t] ++ row) -> string
end) = struct
open M
type global = list (t * string)
type input = source string * option (t * $row)
type filter = source string
val getChoices = List.mapQuery (SELECT * FROM tab AS T)
(fn r => (r.T.nm, render r.T))
fun getChoice k =
r <- oneRow (SELECT T.{{row}} FROM tab AS T WHERE T.{nm} = {[k]});
return r.T
val meta : meta (global, M.t, input, filter) =
{Initialize = getChoices,
Handlers = fn choices =>
NonNull (
{Display = fn (_, kr) => case kr of
None => error Unexpected Foreign null
| Some (k, r) => {[render ({nm = k} ++ r)]},
Edit = fn (s, kr) =>
{List.mapX (fn (k', rend) =>
False
| Some (k, _) =>
k' = k}>{[rend]}
)
choices}
,
Initialize = fn k => s <- source (show k);
r <- rpc (getChoice k);
return (s, Some (k, r)),
Parse = fn (s, _) => k <- signal s; return (read k : option t),
CreateFilter = source "",
DisplayFilter = fn s =>
{List.mapX (fn (k, rend) =>
{[rend]})
choices}
: xbody,
Filter = fn s k => s <- signal s;
return (case read s : option t of
None => True
| Some k' => k' = k)},
{Display = fn (_, kr) => case kr of
None => NULL
| Some (k, r) => {[render ({nm = k} ++ r)]},
Edit = fn (s, kr) =>
True
| _ => False}>NULL
{List.mapX (fn (k', rend) =>
False
| Some (k, _) =>
k' = k}>{[rend]}
)
choices}
,
Initialize = fn k => case k of
None =>
s <- source "";
return (s, None)
| Some k =>
s <- source (show k);
r <- rpc (getChoice k);
return (s, Some (k, r)),
Parse = fn (s, _) => ks <- signal s;
return (case ks of
"" => Some None
| _ => case read ks : option t of
None => None
| Some k => Some (Some k)),
CreateFilter = source "",
DisplayFilter = fn s =>
NULL
{List.mapX (fn (k, rend) =>
{[rend]}
)
choices}
: xbody,
Filter = fn s ko => s <- signal s;
return (case s of
"" => True
| "0" => ko = None
| _ =>
case read (String.substring s {Start = 1,
Len = String.length s - 1})
: option t of
None => True
| Some k => ko = Some k)})}
end
end
con computedState = (unit, xbody, unit)
fun computed [row] [t] (_ : show t) name (f : $row -> t) : colMeta row computedState =
{Initialize = return (),
Handlers = fn () => {Header = name,
Project = fn r => return {[f r]},
Update = fn r _ => return r,
Display = fn x => x,
Edit = fn _ => ...,
Validate = fn _ => return True,
CreateFilter = return (),
DisplayFilter = fn _ => ,
Filter = fn _ _ => return True}}
fun computedHtml [row] name (f : $row -> xbody) : colMeta row computedState =
{Initialize = return (),
Handlers = fn () => {Header = name,
Project = fn r => return (f r),
Update = fn r _ => return r,
Display = fn x => x,
Edit = fn _ => ...,
Validate = fn _ => return True,
CreateFilter = return (),
DisplayFilter = fn _ => ,
Filter = fn _ _ => return True}}
functor Make(M : sig
con key :: {Type}
con row :: {Type}
constraint key ~ row
table tab : (key ++ row)
val raw : $(map rawMeta (key ++ row))
con cols :: {(Type * Type * Type)}
val cols : $(map (colMeta (key ++ row)) cols)
val keyFolder : folder key
val rowFolder : folder row
val colsFolder : folder cols
con aggregates :: {Type}
val aggregates : $(map (aggregateMeta (key ++ row)) aggregates)
val aggFolder : folder aggregates
end) = struct
open Grid.Make(struct
fun keyOf r = r --- M.row
val list = query (SELECT * FROM {{M.tab}} AS T) (fn r rs => return (r.T :: rs)) []
val wholeRow = @Folder.concat ! M.keyFolder M.rowFolder
fun ensql [env] (r : $(M.key ++ M.row)) =
map2 [rawMeta] [id] [sql_exp env [] []]
(fn [t] meta v => @sql_inject meta.Inj v)
[_] wholeRow M.raw r
val new =
row <- Monad.mapR [rawMeta] [id]
(fn [nm :: Name] [t :: Type] meta => meta.New)
[_] wholeRow M.raw;
dml (insert M.tab (ensql row));
return row
fun selector (r : $M.key) : sql_exp [T = M.key ++ M.row] [] [] bool =
foldR2 [rawMeta] [id]
[fn key => rest :: {Type} -> [rest ~ key] => sql_exp [T = key ++ rest] [] [] bool]
(fn [nm :: Name] [t :: Type] [key :: {Type}] [[nm] ~ key]
(meta : rawMeta t) (v : t)
(exp : rest :: {Type} -> [rest ~ key] => sql_exp [T = key ++ rest] [] [] bool)
[rest :: {Type}] [rest ~ [nm = t] ++ key] =>
(WHERE T.{nm} = {@sql_inject meta.Inj v} AND {exp [[nm = t] ++ rest] !}))
(fn [rest :: {Type}] [rest ~ []] => (WHERE TRUE))
[_] M.keyFolder (M.raw --- map rawMeta M.row) r
[_] !
fun save key row =
dml (update [M.key ++ M.row] !
(ensql row)
M.tab
(selector key))
fun delete key =
dml (Basis.delete M.tab (selector key))
val cols = M.cols
val folder = M.colsFolder
val aggregates = M.aggregates
val aggFolder = M.aggFolder
end)
end