diff options
Diffstat (limited to 'lib/ur')
-rw-r--r-- | lib/ur/basis.urs | 1207 | ||||
-rw-r--r-- | lib/ur/char.ur | 19 | ||||
-rw-r--r-- | lib/ur/char.urs | 19 | ||||
-rw-r--r-- | lib/ur/datetime.ur | 135 | ||||
-rw-r--r-- | lib/ur/datetime.urs | 38 | ||||
-rw-r--r-- | lib/ur/json.ur | 387 | ||||
-rw-r--r-- | lib/ur/json.urs | 31 | ||||
-rw-r--r-- | lib/ur/list.ur | 498 | ||||
-rw-r--r-- | lib/ur/list.urs | 116 | ||||
-rw-r--r-- | lib/ur/listPair.ur | 46 | ||||
-rw-r--r-- | lib/ur/listPair.urs | 10 | ||||
-rw-r--r-- | lib/ur/monad.ur | 140 | ||||
-rw-r--r-- | lib/ur/monad.urs | 90 | ||||
-rw-r--r-- | lib/ur/option.ur | 61 | ||||
-rw-r--r-- | lib/ur/option.urs | 16 | ||||
-rw-r--r-- | lib/ur/string.ur | 113 | ||||
-rw-r--r-- | lib/ur/string.urs | 37 | ||||
-rw-r--r-- | lib/ur/top.ur | 430 | ||||
-rw-r--r-- | lib/ur/top.urs | 303 |
19 files changed, 3696 insertions, 0 deletions
diff --git a/lib/ur/basis.urs b/lib/ur/basis.urs new file mode 100644 index 0000000..89a48d5 --- /dev/null +++ b/lib/ur/basis.urs @@ -0,0 +1,1207 @@ +type int +type float +type string +type char +type time +type blob + +type unit = {} + +datatype bool = False | True + +datatype option t = None | Some of t + +datatype list t = Nil | Cons of t * list t + + +(** Polymorphic variants *) + +con variant :: {Type} -> Type +val make : nm :: Name -> t ::: Type -> ts ::: {Type} -> [[nm] ~ ts] => t -> variant ([nm = t] ++ ts) +val match : ts ::: {Type} -> t ::: Type -> variant ts -> $(map (fn t' => t' -> t) ts) -> t + + +(** Basic type classes *) + +class eq +val eq : t ::: Type -> eq t -> t -> t -> bool +val ne : t ::: Type -> eq t -> t -> t -> bool +val eq_int : eq int +val eq_float : eq float +val eq_string : eq string +val eq_char : eq char +val eq_bool : eq bool +val eq_time : eq time +val mkEq : t ::: Type -> (t -> t -> bool) -> eq t + +class num +val zero : t ::: Type -> num t -> t +val neg : t ::: Type -> num t -> t -> t +val plus : t ::: Type -> num t -> t -> t -> t +val minus : t ::: Type -> num t -> t -> t -> t +val times : t ::: Type -> num t -> t -> t -> t +val divide : t ::: Type -> num t -> t -> t -> t +val mod : t ::: Type -> num t -> t -> t -> t +val pow : t ::: Type -> num t -> t -> t -> t +val num_int : num int +val num_float : num float + +class ord +val lt : t ::: Type -> ord t -> t -> t -> bool +val le : t ::: Type -> ord t -> t -> t -> bool +val gt : t ::: Type -> ord t -> t -> t -> bool +val ge : t ::: Type -> ord t -> t -> t -> bool +val ord_int : ord int +val ord_float : ord float +val ord_string : ord string +val ord_char : ord char +val ord_bool : ord bool +val ord_time : ord time +val mkOrd : t ::: Type -> {Lt : t -> t -> bool, Le : t -> t -> bool} -> ord t + + +(** Character operations *) + +val isalnum : char -> bool +val isalpha : char -> bool +val isblank : char -> bool +val iscntrl : char -> bool +val isdigit : char -> bool +val isgraph : char -> bool +val islower : char -> bool +val isprint : char -> bool +val ispunct : char -> bool +val isspace : char -> bool +val isupper : char -> bool +val isxdigit : char -> bool +val tolower : char -> char +val toupper : char -> char +val ord : char -> int +val chr : int -> char + +(** String operations *) + +val strlen : string -> int +val strlenGe : string -> int -> bool +val strcat : string -> string -> string +val strsub : string -> int -> char +val strsuffix : string -> int -> string +val strchr : string -> char -> option string +val strindex : string -> char -> option int +val strsindex : string -> string -> option int +val strcspn : string -> string -> int +val substring : string -> int -> int -> string +val str1 : char -> string + +class show +val show : t ::: Type -> show t -> t -> string +val show_int : show int +val show_float : show float +val show_string : show string +val show_char : show char +val show_bool : show bool +val show_time : show time +val mkShow : t ::: Type -> (t -> string) -> show t + +class read +val read : t ::: Type -> read t -> string -> option t +val readError : t ::: Type -> read t -> string -> t +(* [readError] calls [error] if the input is malformed. *) +val read_int : read int +val read_float : read float +val read_string : read string +val read_char : read char +val read_bool : read bool +val read_time : read time +val mkRead : t ::: Type -> (string -> t) -> (string -> option t) -> read t + + +(** * Monads *) + +class monad :: (Type -> Type) -> Type +val return : m ::: (Type -> Type) -> t ::: Type + -> monad m + -> t -> m t +val bind : m ::: (Type -> Type) -> t1 ::: Type -> t2 ::: Type + -> monad m + -> m t1 -> (t1 -> m t2) + -> m t2 + +val mkMonad : m ::: (Type -> Type) + -> {Return : t ::: Type -> t -> m t, + Bind : t1 ::: Type -> t2 ::: Type -> m t1 -> (t1 -> m t2) -> m t2} + -> monad m + +con transaction :: Type -> Type +val transaction_monad : monad transaction + +con source :: Type -> Type +val source : t ::: Type -> t -> transaction (source t) +val set : t ::: Type -> source t -> t -> transaction unit +val get : t ::: Type -> source t -> transaction t + +con signal :: Type -> Type +val signal_monad : monad signal +val signal : t ::: Type -> source t -> signal t +val current : t ::: Type -> signal t -> transaction t + + +(** * Floats *) + +val float : int -> float +val ceil : float -> int +val trunc : float -> int +val round : float -> int +val floor : float -> int + +(** * Basic Math *) + +val sqrt : float -> float +val sin : float -> float +val cos : float -> float +val log : float -> float +val exp : float -> float +val asin : float -> float +val acos : float -> float +val atan : float -> float +val atan2 : float -> float -> float +val abs: float -> float + +(** * Time *) + +val now : transaction time +val minTime : time +val addSeconds : time -> int -> time +val toSeconds : time -> int +val diffInSeconds : time -> time -> int +(* Earlier time first *) +val toMilliseconds : time -> int +val fromMilliseconds : int -> time +val diffInMilliseconds : time -> time -> int +val timef : string -> time -> string (* Uses strftime() format string *) +val readUtc : string -> option time + +(* Takes a year, month, day, hour, minute, second. *) +val fromDatetime : int -> int -> int -> int -> int -> int -> time +val datetimeYear : time -> int +val datetimeMonth : time -> int +val datetimeDay : time -> int +val datetimeHour : time -> int +val datetimeMinute: time -> int +val datetimeSecond : time -> int +val datetimeDayOfWeek : time -> int + + +(** * Encryption *) + +val crypt : string -> string -> string + + +(** HTTP operations *) + +con http_cookie :: Type -> Type +val getCookie : t ::: Type -> http_cookie t -> transaction (option t) +val setCookie : t ::: Type -> http_cookie t -> {Value : t, + Expires : option time, + Secure : bool} -> transaction unit +val clearCookie : t ::: Type -> http_cookie t -> transaction unit + +type requestHeader +val blessRequestHeader : string -> requestHeader +val checkRequestHeader : string -> option requestHeader +val getHeader : requestHeader -> transaction (option string) + +type responseHeader +val blessResponseHeader : string -> responseHeader +val checkResponseHeader : string -> option responseHeader +val setHeader : responseHeader -> string -> transaction unit + +type envVar +val blessEnvVar : string -> envVar +val checkEnvVar : string -> option envVar +val getenv : envVar -> transaction (option string) + +type meta +val blessMeta : string -> meta +val checkMeta : string -> option meta + + +(** JavaScript-y gadgets *) + +val alert : string -> transaction unit +val confirm : string -> transaction bool +val spawn : transaction unit -> transaction unit +val sleep : int -> transaction unit + +val rpc : t ::: Type -> transaction t -> transaction t +val tryRpc : t ::: Type -> transaction t -> transaction (option t) +(* Returns [None] on error condition. *) + + +(** Channels *) + +con channel :: Type -> Type +val channel : t ::: Type -> transaction (channel t) +val send : t ::: Type -> channel t -> t -> transaction unit +val recv : t ::: Type -> channel t -> transaction t + +type client +val self : transaction client + + +(** SQL *) + +con sql_table :: {Type} -> {{Unit}} -> Type +con sql_view :: {Type} -> Type + +class fieldsOf :: Type -> {Type} -> Type +val fieldsOf_table : fs ::: {Type} -> keys ::: {{Unit}} + -> fieldsOf (sql_table fs keys) fs +val fieldsOf_view : fs ::: {Type} + -> fieldsOf (sql_view fs) fs + +(*** Constraints *) + +(**** Primary keys *) + +class sql_injectable_prim +val sql_bool : sql_injectable_prim bool +val sql_int : sql_injectable_prim int +val sql_float : sql_injectable_prim float +val sql_string : sql_injectable_prim string +val sql_char : sql_injectable_prim char +val sql_time : sql_injectable_prim time +val sql_blob : sql_injectable_prim blob +val sql_channel : t ::: Type -> sql_injectable_prim (channel t) +val sql_client : sql_injectable_prim client + +con serialized :: Type -> Type +val serialize : t ::: Type -> t -> serialized t +val deserialize : t ::: Type -> serialized t -> t +val sql_serialized : t ::: Type -> sql_injectable_prim (serialized t) + +con primary_key :: {Type} -> {{Unit}} -> Type +val no_primary_key : fs ::: {Type} -> primary_key fs [] +val primary_key : rest ::: {Type} -> t ::: Type -> key1 :: Name -> keys :: {Type} + -> [[key1] ~ keys] => [[key1 = t] ++ keys ~ rest] + => $([key1 = sql_injectable_prim t] ++ map sql_injectable_prim keys) + -> primary_key ([key1 = t] ++ keys ++ rest) + [Pkey = [key1] ++ map (fn _ => ()) keys] + +(**** Other constraints *) + +con sql_constraints :: {Type} -> {{Unit}} -> Type +(* Arguments: column types, uniqueness implications of constraints *) + +con sql_constraint :: {Type} -> {Unit} -> Type + +val no_constraint : fs ::: {Type} -> sql_constraints fs [] +val one_constraint : fs ::: {Type} -> unique ::: {Unit} -> name :: Name + -> sql_constraint fs unique + -> sql_constraints fs [name = unique] +val join_constraints : fs ::: {Type} + -> uniques1 ::: {{Unit}} -> uniques2 ::: {{Unit}} -> [uniques1 ~ uniques2] + => sql_constraints fs uniques1 -> sql_constraints fs uniques2 + -> sql_constraints fs (uniques1 ++ uniques2) + + +val unique : rest ::: {Type} -> t ::: Type -> unique1 :: Name -> unique :: {Type} + -> [[unique1] ~ unique] => [[unique1 = t] ++ unique ~ rest] + => sql_constraint ([unique1 = t] ++ unique ++ rest) ([unique1] ++ map (fn _ => ()) unique) + +class linkable :: Type -> Type -> Type +val linkable_same : t ::: Type -> linkable t t +val linkable_from_nullable : t ::: Type -> linkable (option t) t +val linkable_to_nullable : t ::: Type -> linkable t (option t) + +con matching :: {Type} -> {Type} -> Type +val mat_nil : matching [] [] +val mat_cons : t1 ::: Type -> rest1 ::: {Type} -> t2 ::: Type -> rest2 ::: {Type} + -> nm1 :: Name -> nm2 :: Name + -> [[nm1] ~ rest1] => [[nm2] ~ rest2] + => linkable t1 t2 + -> matching rest1 rest2 + -> matching ([nm1 = t1] ++ rest1) ([nm2 = t2] ++ rest2) + +con propagation_mode :: {Type} -> Type +val restrict : fs ::: {Type} -> propagation_mode fs +val cascade : fs ::: {Type} -> propagation_mode fs +val no_action : fs ::: {Type} -> propagation_mode fs +val set_null : fs ::: {Type} -> propagation_mode (map option fs) + + +val foreign_key : mine1 ::: Name -> t ::: Type -> mine ::: {Type} -> munused ::: {Type} + -> foreign ::: {Type} -> funused ::: {Type} + -> nm ::: Name -> uniques ::: {{Unit}} + -> [[mine1] ~ mine] => [[mine1 = t] ++ mine ~ munused] + => [foreign ~ funused] => [[nm] ~ uniques] + => matching ([mine1 = t] ++ mine) foreign + -> sql_table (foreign ++ funused) ([nm = map (fn _ => ()) foreign] ++ uniques) + -> {OnDelete : propagation_mode ([mine1 = t] ++ mine), + OnUpdate : propagation_mode ([mine1 = t] ++ mine)} + -> sql_constraint ([mine1 = t] ++ mine ++ munused) [] + +con sql_exp :: {{Type}} -> {{Type}} -> {Type} -> Type -> Type +val sql_exp_weaken : fs ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} -> t ::: Type + -> fs' ::: {{Type}} -> agg' ::: {{Type}} -> exps' ::: {Type} + -> [fs ~ fs'] => [agg ~ agg'] => [exps ~ exps'] => + sql_exp fs agg exps t + -> sql_exp (fs ++ fs') (agg ++ agg') (exps ++ exps') t + +val check : fs ::: {Type} + -> sql_exp [] [] fs bool + -> sql_constraint fs [] + + +(*** Queries *) + +con sql_query :: {{Type}} -> {{Type}} -> {{Type}} -> {Type} -> Type +con sql_query1 :: {{Type}} -> {{Type}} -> {{Type}} -> {{Type}} -> {Type} -> Type + +con sql_subset :: {{Type}} -> {{Type}} -> Type +val sql_subset : keep_drop :: {({Type} * {Type})} + -> sql_subset + (map (fn fields :: ({Type} * {Type}) => fields.1 ++ fields.2) keep_drop) + (map (fn fields :: ({Type} * {Type}) => fields.1) keep_drop) +val sql_subset_all : tables :: {{Type}} -> sql_subset tables tables +val sql_subset_concat : big1 ::: {{Type}} -> little1 ::: {{Type}} + -> big2 ::: {{Type}} -> little2 ::: {{Type}} + -> [big1 ~ big2] => [little1 ~ little2] => + sql_subset big1 little1 + -> sql_subset big2 little2 + -> sql_subset (big1 ++ big2) (little1 ++ little2) + +con sql_from_items :: {{Type}} -> {{Type}} -> Type + +val sql_from_nil : free ::: {{Type}} -> sql_from_items free [] +val sql_from_table : free ::: {{Type}} -> t ::: Type -> fs ::: {Type} + -> fieldsOf t fs -> name :: Name + -> t -> sql_from_items free [name = fs] +val sql_from_query : free ::: {{Type}} -> fs ::: {Type} -> name :: Name + -> sql_query free [] [] fs + -> sql_from_items free [name = fs] +val sql_from_comma : free ::: {{Type}} -> tabs1 ::: {{Type}} -> tabs2 ::: {{Type}} + -> [tabs1 ~ tabs2] + => sql_from_items free tabs1 -> sql_from_items free tabs2 + -> sql_from_items free (tabs1 ++ tabs2) +val sql_inner_join : free ::: {{Type}} -> tabs1 ::: {{Type}} -> tabs2 ::: {{Type}} + -> [free ~ tabs1] => [free ~ tabs2] => [tabs1 ~ tabs2] + => sql_from_items free tabs1 -> sql_from_items free tabs2 + -> sql_exp (free ++ tabs1 ++ tabs2) [] [] bool + -> sql_from_items free (tabs1 ++ tabs2) + +class nullify :: Type -> Type -> Type +val nullify_option : t ::: Type -> nullify (option t) (option t) +val nullify_prim : t ::: Type -> sql_injectable_prim t -> nullify t (option t) + +val sql_left_join : free ::: {{Type}} -> tabs1 ::: {{Type}} -> tabs2 ::: {{(Type * Type)}} + -> [free ~ tabs1] => [free ~ tabs2] => [tabs1 ~ tabs2] + => $(map (fn r => $(map (fn p :: (Type * Type) => nullify p.1 p.2) r)) tabs2) + -> sql_from_items free tabs1 -> sql_from_items free (map (map (fn p :: (Type * Type) => p.1)) tabs2) + -> sql_exp (free ++ tabs1 ++ map (map (fn p :: (Type * Type) => p.1)) tabs2) [] [] bool + -> sql_from_items free (tabs1 ++ map (map (fn p :: (Type * Type) => p.2)) tabs2) + +val sql_right_join : free ::: {{Type}} -> tabs1 ::: {{(Type * Type)}} -> tabs2 ::: {{Type}} + -> [free ~ tabs1] => [free ~ tabs2] => [tabs1 ~ tabs2] + => $(map (fn r => $(map (fn p :: (Type * Type) => nullify p.1 p.2) r)) tabs1) + -> sql_from_items free (map (map (fn p :: (Type * Type) => p.1)) tabs1) -> sql_from_items free tabs2 + -> sql_exp (free ++ map (map (fn p :: (Type * Type) => p.1)) tabs1 ++ tabs2) [] [] bool + -> sql_from_items free (map (map (fn p :: (Type * Type) => p.2)) tabs1 ++ tabs2) + +val sql_full_join : free ::: {{Type}} -> tabs1 ::: {{(Type * Type)}} -> tabs2 ::: {{(Type * Type)}} + -> [free ~ tabs1] => [free ~ tabs2] => [tabs1 ~ tabs2] + => $(map (fn r => $(map (fn p :: (Type * Type) => nullify p.1 p.2) r)) (tabs1 ++ tabs2)) + -> sql_from_items free (map (map (fn p :: (Type * Type) => p.1)) tabs1) + -> sql_from_items free (map (map (fn p :: (Type * Type) => p.1)) tabs2) + -> sql_exp (free ++ map (map (fn p :: (Type * Type) => p.1)) (tabs1 ++ tabs2)) [] [] bool + -> sql_from_items free (map (map (fn p :: (Type * Type) => p.2)) (tabs1 ++ tabs2)) + +(** [ORDER BY] and [SELECT] expressions may use window functions, so we introduce a type family for such expressions. *) +con sql_expw :: {{Type}} -> {{Type}} -> {Type} -> Type -> Type + +val sql_query1 : free ::: {{Type}} + -> afree ::: {{Type}} + -> tables ::: {{Type}} + -> grouped ::: {{Type}} + -> selectedFields ::: {{Type}} + -> selectedExps ::: {Type} + -> empties :: {Unit} + -> [free ~ tables] + => [free ~ grouped] + => [afree ~ tables] + => [empties ~ selectedFields] + => {Distinct : bool, + From : sql_from_items free tables, + Where : sql_exp (free ++ tables) afree [] bool, + GroupBy : sql_subset tables grouped, + Having : sql_exp (free ++ grouped) (afree ++ tables) [] bool, + SelectFields : sql_subset grouped (map (fn _ => []) empties ++ selectedFields), + SelectExps : $(map (sql_expw (free ++ grouped) (afree ++ tables) []) + selectedExps) } + -> sql_query1 free afree tables selectedFields selectedExps + +type sql_relop +val sql_union : sql_relop +val sql_intersect : sql_relop +val sql_except : sql_relop +val sql_relop : free ::: {{Type}} + -> afree ::: {{Type}} + -> tables1 ::: {{Type}} + -> tables2 ::: {{Type}} + -> selectedFields ::: {{Type}} + -> selectedExps ::: {Type} + -> sql_relop + -> bool (* ALL *) + -> sql_query1 free afree tables1 selectedFields selectedExps + -> sql_query1 free afree tables2 selectedFields selectedExps + -> sql_query1 free afree [] selectedFields selectedExps +val sql_forget_tables : free ::: {{Type}} -> afree ::: {{Type}} -> tables ::: {{Type}} -> selectedFields ::: {{Type}} -> selectedExps ::: {Type} + -> sql_query1 free afree tables selectedFields selectedExps + -> sql_query1 free afree [] selectedFields selectedExps + +type sql_direction +val sql_asc : sql_direction +val sql_desc : sql_direction + +(** This type class supports automatic injection of either regular or window expressions into [sql_expw]. *) +class sql_window :: ({{Type}} -> {{Type}} -> {Type} -> Type -> Type) -> Type +val sql_window_normal : sql_window sql_exp +val sql_window_fancy : sql_window sql_expw +val sql_window : tf ::: ({{Type}} -> {{Type}} -> {Type} -> Type -> Type) + -> tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} -> t ::: Type + -> sql_window tf + -> tf tables agg exps t + -> sql_expw tables agg exps t + +con sql_order_by :: {{Type}} -> {Type} -> Type +val sql_order_by_Nil : tables ::: {{Type}} -> exps :: {Type} -> sql_order_by tables exps +val sql_order_by_Cons : tf ::: ({{Type}} -> {{Type}} -> {Type} -> Type -> Type) -> tables ::: {{Type}} -> exps ::: {Type} -> t ::: Type + -> sql_window tf + -> tf tables [] exps t -> sql_direction + -> sql_order_by tables exps + -> sql_order_by tables exps +val sql_order_by_random : tables ::: {{Type}} -> exps ::: {Type} + -> sql_order_by tables exps + +type sql_limit +val sql_no_limit : sql_limit +val sql_limit : int -> sql_limit + +type sql_offset +val sql_no_offset : sql_offset +val sql_offset : int -> sql_offset + +val sql_query : free ::: {{Type}} + -> afree ::: {{Type}} + -> tables ::: {{Type}} + -> selectedFields ::: {{Type}} + -> selectedExps ::: {Type} + -> [free ~ tables] + => {Rows : sql_query1 free afree tables selectedFields selectedExps, + OrderBy : sql_order_by (free ++ tables) selectedExps, + Limit : sql_limit, + Offset : sql_offset} + -> sql_query free afree selectedFields selectedExps + +val sql_field : otherTabs ::: {{Type}} -> otherFields ::: {Type} + -> fieldType ::: Type -> agg ::: {{Type}} + -> exps ::: {Type} + -> tab :: Name -> field :: Name + -> sql_exp + ([tab = [field = fieldType] ++ otherFields] ++ otherTabs) + agg exps fieldType + +val sql_exp : tabs ::: {{Type}} -> agg ::: {{Type}} -> t ::: Type -> rest ::: {Type} + -> nm :: Name + -> sql_exp tabs agg ([nm = t] ++ rest) t + +class sql_injectable +val sql_prim : t ::: Type -> sql_injectable_prim t -> sql_injectable t +val sql_option_prim : t ::: Type -> sql_injectable_prim t -> sql_injectable (option t) + +val sql_inject : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> t ::: Type + -> sql_injectable t -> t -> sql_exp tables agg exps t + +val sql_is_null : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> t ::: Type + -> sql_exp tables agg exps (option t) + -> sql_exp tables agg exps bool + +val sql_coalesce : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> t ::: Type + -> sql_exp tables agg exps (option t) + -> sql_exp tables agg exps t + -> sql_exp tables agg exps t + +val sql_if_then_else : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> t ::: Type + -> sql_exp tables agg exps bool + -> sql_exp tables agg exps t + -> sql_exp tables agg exps t + -> sql_exp tables agg exps t + +class sql_arith +val sql_arith_int : sql_arith int +val sql_arith_float : sql_arith float +val sql_arith_option : t ::: Type -> sql_arith t -> sql_arith (option t) + +con sql_unary :: Type -> Type -> Type +val sql_not : sql_unary bool bool +val sql_unary : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> arg ::: Type -> res ::: Type + -> sql_unary arg res -> sql_exp tables agg exps arg + -> sql_exp tables agg exps res + +val sql_neg : t ::: Type -> sql_arith t -> sql_unary t t + +con sql_binary :: Type -> Type -> Type -> Type +val sql_and : sql_binary bool bool bool +val sql_or : sql_binary bool bool bool +val sql_binary : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> arg1 ::: Type -> arg2 ::: Type -> res ::: Type + -> sql_binary arg1 arg2 res -> sql_exp tables agg exps arg1 + -> sql_exp tables agg exps arg2 + -> sql_exp tables agg exps res + +val sql_plus : t ::: Type -> sql_arith t -> sql_binary t t t +val sql_minus : t ::: Type -> sql_arith t -> sql_binary t t t +val sql_times : t ::: Type -> sql_arith t -> sql_binary t t t +val sql_div : t ::: Type -> sql_arith t -> sql_binary t t t +val sql_mod : sql_binary int int int + +val sql_eq : t ::: Type -> sql_binary t t bool +(* Note that the semantics of this operator on nullable types are different than for standard SQL! + * Instead, we do it the sane way, where [NULL = NULL]. *) + +val sql_ne : t ::: Type -> sql_binary t t bool +val sql_lt : t ::: Type -> sql_binary t t bool +val sql_le : t ::: Type -> sql_binary t t bool +val sql_gt : t ::: Type -> sql_binary t t bool +val sql_ge : t ::: Type -> sql_binary t t bool + +val sql_like : sql_binary string string bool + +val sql_count : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> sql_exp tables agg exps int + +con sql_aggregate :: Type -> Type -> Type +val sql_aggregate : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> dom ::: Type -> ran ::: Type + -> sql_aggregate dom ran -> sql_exp agg agg exps dom + -> sql_exp tables agg exps ran + +val sql_count_col : t ::: Type -> sql_aggregate (option t) int + +class sql_summable +val sql_summable_int : sql_summable int +val sql_summable_float : sql_summable float +val sql_summable_option : t ::: Type -> sql_summable t -> sql_summable (option t) +val sql_avg : t ::: Type -> sql_summable t -> sql_aggregate t (option float) +val sql_sum : t ::: Type -> nt ::: Type -> sql_summable t -> nullify t nt -> sql_aggregate t nt + +class sql_maxable +val sql_maxable_int : sql_maxable int +val sql_maxable_float : sql_maxable float +val sql_maxable_string : sql_maxable string +val sql_maxable_time : sql_maxable time +val sql_maxable_option : t ::: Type -> sql_maxable t -> sql_maxable (option t) +val sql_max : t ::: Type -> nt ::: Type -> sql_maxable t -> nullify t nt -> sql_aggregate t nt +val sql_min : t ::: Type -> nt ::: Type -> sql_maxable t -> nullify t nt -> sql_aggregate t nt + +con sql_nfunc :: Type -> Type +val sql_nfunc : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> t ::: Type + -> sql_nfunc t -> sql_exp tables agg exps t +val sql_current_timestamp : sql_nfunc time + +con sql_ufunc :: Type -> Type -> Type +val sql_ufunc : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> dom ::: Type -> ran ::: Type + -> sql_ufunc dom ran -> sql_exp tables agg exps dom + -> sql_exp tables agg exps ran +val sql_octet_length : sql_ufunc blob int +val sql_known : t ::: Type -> sql_ufunc t bool +val sql_lower : sql_ufunc string string +val sql_upper : sql_ufunc string string + +val sql_nullable : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} -> t ::: Type + -> sql_injectable_prim t + -> sql_exp tables agg exps t + -> sql_exp tables agg exps (option t) + +val sql_subquery : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} -> nm ::: Name -> t ::: Type -> nt ::: Type + -> nullify t nt + -> sql_query tables agg [] [nm = t] + -> sql_exp tables agg exps nt + +(** Window function expressions *) + +con sql_partition :: {{Type}} -> {{Type}} -> {Type} -> Type +val sql_no_partition : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> sql_partition tables agg exps +val sql_partition : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} -> t ::: Type + -> sql_exp tables agg exps t + -> sql_partition tables agg exps + +con sql_window_function :: {{Type}} -> {{Type}} -> {Type} -> Type -> Type +val sql_window_function : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> t ::: Type + -> sql_window_function tables agg exps t + -> sql_partition tables agg exps + -> sql_order_by tables exps + -> sql_expw tables agg exps t + +val sql_window_aggregate : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> t ::: Type -> nt ::: Type + -> sql_aggregate t nt + -> sql_exp tables agg exps t + -> sql_window_function tables agg exps nt +val sql_window_count : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> sql_window_function tables agg exps int +val sql_rank : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> sql_window_function tables agg exps int + + +(*** Executing queries *) + +val query : tables ::: {{Type}} -> exps ::: {Type} + -> [tables ~ exps] => + state ::: Type + -> sql_query [] [] tables exps + -> ($(exps ++ map (fn fields :: {Type} => $fields) tables) + -> state + -> transaction state) + -> state + -> transaction state + +val show_sql_query : freeTables ::: {{Type}} -> freeAggs ::: {{Type}} -> tables ::: {{Type}} -> exps ::: {Type} + -> show (sql_query freeTables freeAggs tables exps) + + +(*** Database mutators *) + +type dml +val dml : dml -> transaction unit +val tryDml : dml -> transaction (option string) +(* Returns an error message on failure. *) + +val insert : fields ::: {Type} -> uniques ::: {{Unit}} + -> sql_table fields uniques + -> $(map (fn t :: Type => sql_exp [] [] [] t) fields) + -> dml + +val update : unchanged ::: {Type} -> uniques ::: {{Unit}} -> changed :: {Type} -> + [changed ~ unchanged] => + $(map (fn t :: Type => sql_exp [T = changed ++ unchanged] [] [] t) changed) + -> sql_table (changed ++ unchanged) uniques + -> sql_exp [T = changed ++ unchanged] [] [] bool + -> dml + +val delete : fields ::: {Type} -> uniques ::: {{Unit}} + -> sql_table fields uniques + -> sql_exp [T = fields] [] [] bool + -> dml + +(*** Sequences *) + +type sql_sequence +val nextval : sql_sequence -> transaction int +val setval : sql_sequence -> int -> transaction unit + + +(** XML *) + +type css_class +val show_css_class : show css_class +val null : css_class +(* No special formatting *) +val classes : css_class -> css_class -> css_class +(* The equivalent of writing one class after the other, separated by a space, in + * an HTML 'class' attribute *) + +type css_value +val atom : string -> css_value +type url +val css_url : url -> css_value +val sql_url : sql_injectable_prim url +type css_property +val property : string -> css_property +val value : css_property -> css_value -> css_property +type css_style +val noStyle : css_style +val oneProperty : css_style -> css_property -> css_style + +con tag :: {Type} -> {Unit} -> {Unit} -> {Type} -> {Type} -> Type + +con xml :: {Unit} -> {Type} -> {Type} -> Type +val cdata : ctx ::: {Unit} -> use ::: {Type} -> string -> xml ctx use [] +val cdataChar : ctx ::: {Unit} -> use ::: {Type} -> char -> xml ctx use [] +val tag : attrsGiven ::: {Type} -> attrsAbsent ::: {Type} + -> ctxOuter ::: {Unit} -> ctxInner ::: {Unit} + -> useOuter ::: {Type} -> useInner ::: {Type} + -> bindOuter ::: {Type} -> bindInner ::: {Type} + -> [attrsGiven ~ attrsAbsent] => + [useOuter ~ useInner] => + [bindOuter ~ bindInner] => + css_class + -> option (signal css_class) + -> css_style + -> option (signal css_style) + -> $attrsGiven + -> tag (attrsGiven ++ attrsAbsent) + ctxOuter ctxInner useOuter bindOuter + -> xml ctxInner useInner bindInner + -> xml ctxOuter (useOuter ++ useInner) (bindOuter ++ bindInner) +val join : ctx ::: {Unit} + -> use1 ::: {Type} -> bind1 ::: {Type} -> bind2 ::: {Type} + -> [use1 ~ bind1] => [bind1 ~ bind2] => + xml ctx use1 bind1 + -> xml ctx (use1 ++ bind1) bind2 + -> xml ctx use1 (bind1 ++ bind2) +val useMore : ctx ::: {Unit} -> use1 ::: {Type} -> use2 ::: {Type} + -> bind ::: {Type} + -> [use1 ~ use2] => + xml ctx use1 bind + -> xml ctx (use1 ++ use2) bind + +con html = [Html] +con head = [Head] + +con body' = [MakeForm, Body] +con form' = [Body, Form] +con subform' = [Body, Subform] +con tabl' = [MakeForm, Table] +con tr' = [MakeForm, Tr] + +con body = [Dyn] ++ body' +con form = [Dyn] ++ form' +con subform = [Dyn] ++ subform' +con tabl = [Dyn] ++ tabl' +con tr = [Dyn] ++ tr' + +con xhtml = xml html +con page = xhtml [] [] +con xbody = xml body [] [] +con xhead = xml head [] [] +con xtable = xml tabl [] [] +con xtr = xml tr [] [] +con xform = xml form [] [] + + +(*** HTML details *) + +type queryString +val show_queryString : show queryString + +val show_url : show url +val bless : string -> url +val checkUrl : string -> option url +val currentUrl : transaction url +val currentUrlHasPost : transaction bool +val currentUrlHasQueryString : transaction bool +val url : transaction page -> url +val effectfulUrl : (option queryString -> transaction page) -> url +val redirect : t ::: Type -> url -> transaction t + +type id +val fresh : transaction id +val giveFocus : id -> transaction unit +val show_id : show id + +val dyn : ctx ::: {Unit} -> use ::: {Type} -> bind ::: {Type} -> [ctx ~ [Dyn]] => unit + -> tag [Signal = signal (xml ([Dyn] ++ ctx) use bind)] ([Dyn] ++ ctx) [] use bind + +val active : unit + -> tag [Code = transaction xbody] body [] [] [] + +val script : unit + -> tag [Code = transaction unit] head [] [] [] + +(* Type for HTML5 "data-*" and "aria-*" attributes. *) +type data_attr_kind +val data_kind : data_attr_kind +val aria_kind : data_attr_kind + +type data_attr +val data_attr : data_attr_kind -> string (* Key *) -> string (* Value *) -> data_attr +(* This function will fail if the key doesn't meet HTML's lexical rules! *) +val data_attrs : data_attr -> data_attr -> data_attr + +val head : unit -> tag [Data = data_attr] html head [] [] +val title : unit -> tag [Data = data_attr] head [] [] [] +val link : unit -> tag [Data = data_attr, Id = id, Rel = string, Typ = string, Href = url, Media = string, Integrity = string, Crossorigin = string] head [] [] [] +val meta : unit -> tag [Nam = meta, Content = string, Id = id] head [] [] [] + +datatype mouseButton = Left | Right | Middle + +type mouseEvent = { ScreenX : int, ScreenY : int, ClientX : int, ClientY : int, + CtrlKey : bool, ShiftKey : bool, AltKey : bool, MetaKey : bool, + Button : mouseButton } + +con mouseEvents = map (fn _ :: Unit => mouseEvent -> transaction unit) + [Onclick, Oncontextmenu, Ondblclick, Onmousedown, Onmouseenter, Onmouseleave, Onmousemove, Onmouseout, Onmouseover, Onmouseup] + +type keyEvent = { KeyCode : int, + CtrlKey : bool, ShiftKey : bool, AltKey : bool, MetaKey : bool } + +con keyEvents = map (fn _ :: Unit => keyEvent -> transaction unit) + [Onkeydown, Onkeypress, Onkeyup] + +val body : unit -> tag ([Data = data_attr, Onload = transaction unit, Onresize = transaction unit, Onunload = transaction unit, Onhashchange = transaction unit] + ++ mouseEvents ++ keyEvents) + html body [] [] + +con bodyTag = fn (attrs :: {Type}) => + ctx ::: {Unit} -> + [[Body] ~ ctx] => + unit -> tag attrs ([Body] ++ ctx) ([Body] ++ ctx) [] [] +con bodyTagStandalone = fn (attrs :: {Type}) => + ctx ::: {Unit} + -> [[Body] ~ ctx] => + unit -> tag attrs ([Body] ++ ctx) [] [] [] + +val br : bodyTagStandalone [Data = data_attr, Id = id] + +con focusEvents = [Onblur = transaction unit, Onfocus = transaction unit] + + +(* Key arguments are character codes. *) +con resizeEvents = [Onresize = transaction unit] +con scrollEvents = [Onscroll = transaction unit] + +con boxEvents = focusEvents ++ mouseEvents ++ keyEvents ++ resizeEvents ++ scrollEvents +con tableEvents = focusEvents ++ mouseEvents ++ keyEvents + +con boxAttrs = [Data = data_attr, Id = id, Title = string, Role = string, Align = string] ++ boxEvents +con tableAttrs = [Data = data_attr, Id = id, Title = string, Align = string] ++ tableEvents + +val span : bodyTag boxAttrs +val div : bodyTag boxAttrs + +val p : bodyTag boxAttrs +val strong : bodyTag boxAttrs +val em : bodyTag boxAttrs +val b : bodyTag boxAttrs +val i : bodyTag boxAttrs +val tt : bodyTag boxAttrs +val sub : bodyTag boxAttrs +val sup : bodyTag boxAttrs + +val h1 : bodyTag boxAttrs +val h2 : bodyTag boxAttrs +val h3 : bodyTag boxAttrs +val h4 : bodyTag boxAttrs +val h5 : bodyTag boxAttrs +val h6 : bodyTag boxAttrs + +val li : bodyTag boxAttrs +val ol : bodyTag boxAttrs +val ul : bodyTag boxAttrs + +val hr : bodyTag boxAttrs + +val pre : bodyTag boxAttrs + +(** sections **) +val section : bodyTag boxAttrs +val article : bodyTag boxAttrs +val nav : bodyTag boxAttrs +val aside : bodyTag boxAttrs +val footer : bodyTag boxAttrs +val header : bodyTag boxAttrs +val main : bodyTag boxAttrs + +(** forms **) +val meter : bodyTag boxAttrs +val progress : bodyTag boxAttrs +val output : bodyTag boxAttrs +val keygen : bodyTag boxAttrs +val datalist : bodyTag boxAttrs + +(** Interactive Elements **) +val details : bodyTag boxAttrs +val dialog : bodyTag boxAttrs +val menuitem : bodyTag boxAttrs + +(** Grouping Content **) +val figure : bodyTag boxAttrs +val figcaption : bodyTag boxAttrs + +(** Text Level Semantics **) +val data : bodyTag boxAttrs +val mark : bodyTag boxAttrs +val rp : bodyTag boxAttrs +val rt : bodyTag boxAttrs +val ruby : bodyTag boxAttrs +val summary : bodyTag boxAttrs +val time : bodyTag boxAttrs +val wbr : bodyTag boxAttrs +val bdi : bodyTag boxAttrs + +val a : bodyTag ([Link = transaction page, Href = url, Target = string, Rel = string, Download = string] ++ boxAttrs) + +val img : bodyTag ([Alt = string, Src = url, Width = int, Height = int, + Onabort = transaction unit, Onerror = transaction unit, + Onload = transaction unit] ++ boxAttrs) + +val form : ctx ::: {Unit} -> bind ::: {Type} + -> [[MakeForm, Form] ~ ctx] => + option id + -> option css_class + -> xml ([Form] ++ ctx) [] bind + -> xml ([MakeForm] ++ ctx) [] [] + +val subform : ctx ::: {Unit} -> use ::: {Type} -> bind ::: {Type} + -> [[Form] ~ ctx] => + nm :: Name -> [[nm] ~ use] => + xml ([Form] ++ ctx) [] bind + -> xml ([Form] ++ ctx) use [nm = $bind] + +val subforms : ctx ::: {Unit} -> use ::: {Type} -> bind ::: {Type} + -> [[Form, Subform] ~ ctx] => + nm :: Name -> [[nm] ~ use] => + xml ([Subform] ++ ctx) [Entry = $bind] [] + -> xml ([Form] ++ ctx) use [nm = list ($bind)] + +val entry : ctx ::: {Unit} -> bind ::: {Type} + -> [[Subform, Form] ~ ctx] => + xml ([Form] ++ ctx) [] bind + -> xml ([Subform] ++ ctx) [Entry = $bind] [] + +con formTag = fn (ty :: Type) (inner :: {Unit}) (attrs :: {Type}) => + ctx ::: {Unit} + -> [[Form] ~ ctx] => + nm :: Name -> unit + -> tag attrs ([Form] ++ ctx) inner [] [nm = ty] + +con inputAttrs = [Required = bool, Autofocus = bool] + + +val hidden : formTag string [] [Data = data_attr, Id = string, Value = string] +val textbox : formTag string [] ([Value = string, Size = int, Placeholder = string, Source = source string, Onchange = transaction unit, + Ontext = transaction unit] ++ boxAttrs ++ inputAttrs) +val password : formTag string [] ([Value = string, Size = int, Placeholder = string, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) +val textarea : formTag string [] ([Rows = int, Cols = int, Placeholder = string, Onchange = transaction unit, + Ontext = transaction unit] ++ boxAttrs ++ inputAttrs) + +val checkbox : formTag bool [] ([Checked = bool, Onchange = transaction unit] ++ boxAttrs) + +(* HTML5 widgets galore! *) + +type textWidget = formTag string [] ([Value = string, Size = int, Placeholder = string, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) + +val email : textWidget +val search : textWidget +val url_ : textWidget +val tel : textWidget +val color : textWidget + +val number : formTag float [] ([Value = float, Min = float, Max = float, Step = float, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) +val range : formTag float [] ([Value = float, Min = float, Max = float, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) +val date : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) +val datetime : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) +val datetime_local : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) +val month : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) +val week : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) +val timeInput : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) + + + +type file +val fileName : file -> option string +val fileMimeType : file -> string +val fileData : file -> blob + +val upload : formTag file [] ([Value = string, Size = int] ++ boxAttrs) + +type mimeType +val blessMime : string -> mimeType +val checkMime : string -> option mimeType +val returnBlob : t ::: Type -> blob -> mimeType -> transaction t +val blobSize : blob -> int +val textBlob : string -> blob + +type postBody +val postType : postBody -> string +val postData : postBody -> string + +type postField +val firstFormField : string -> option postField +val fieldName : postField -> string +val fieldValue : postField -> string +val remainingFields : postField -> string + +con radio = [Body, Radio] +val radio : formTag (option string) radio [Data = data_attr, Id = id] +val radioOption : unit -> tag ([Value = string, Checked = bool, Onchange = transaction unit] ++ boxAttrs) radio [] [] [] + +con select = [Select] +val select : formTag string select ([Onchange = transaction unit] ++ boxAttrs) +val option : unit -> tag [Data = data_attr, Value = string, Selected = bool] select [] [] [] + +val submit : ctx ::: {Unit} -> use ::: {Type} + -> [[Form] ~ ctx] => + unit + -> tag ([Value = string, Action = $use -> transaction page] ++ boxAttrs) + ([Form] ++ ctx) ([Form] ++ ctx) use [] + +val image : ctx ::: {Unit} -> use ::: {Type} + -> [[Form] ~ ctx] => + unit + -> tag ([Src = url, Width = int, Height = int, Alt = string, Action = $use -> transaction page] ++ boxAttrs) + ([Form] ++ ctx) ([Form] ++ ctx) use [] + +val label : bodyTag ([For = id, Accesskey = string] ++ tableAttrs) + +val fieldset : bodyTag boxAttrs +val legend : bodyTag boxAttrs + + +(*** AJAX-oriented widgets *) + +con cformTag = fn (attrs :: {Type}) (inner :: {Unit}) => + ctx ::: {Unit} + -> [[Body] ~ ctx] => [[Body] ~ inner] => + unit -> tag attrs ([Body] ++ ctx) ([Body] ++ inner) [] [] + +type ctext = cformTag ([Value = string, Size = int, Source = source string, Placeholder = string, + Onchange = transaction unit, Ontext = transaction unit] ++ boxAttrs ++ inputAttrs) [] + +val ctextbox : ctext +val cpassword : ctext +val cemail : ctext +val csearch : ctext +val curl : ctext +val ctel : ctext +val ccolor : ctext + +val cnumber : cformTag ([Source = source (option float), Min = float, Max = float, Step = float, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) [] +val crange : cformTag ([Source = source (option float), Min = float, Max = float, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) [] +val cdate : cformTag ([Source = source string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) [] +val cdatetime : cformTag ([Source = source string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) [] +val cdatetime_local : cformTag ([Source = source string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) [] +val cmonth : cformTag ([Source = source string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) [] +val cweek : cformTag ([Source = source string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) [] +val ctime : cformTag ([Source = source string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) [] + +val button : cformTag ([Value = string, Disabled = bool] ++ boxAttrs) [] + +val ccheckbox : cformTag ([Size = int, Source = source bool, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) [] + +val cselect : cformTag ([Source = source string, Onchange = transaction unit] ++ boxAttrs) [Cselect] +val coption : unit -> tag [Value = string, Selected = bool] [Cselect, Body] [] [] [] + +val ctextarea : cformTag ([Rows = int, Cols = int, Placeholder = string, Source = source string, Onchange = transaction unit, + Ontext = transaction unit] ++ boxAttrs ++ inputAttrs) [] + +(*** Tables *) + +val tabl : other ::: {Unit} -> [other ~ [Body, Table]] => unit + -> tag ([Border = int] ++ boxAttrs) + ([Body] ++ other) ([Table] ++ other) [] [] +val tr : other ::: {Unit} -> [other ~ [Table, Tr]] => unit + -> tag tableAttrs + ([Table] ++ other) ([Tr] ++ other) [] [] +val th : other ::: {Unit} -> [other ~ [Body, Tr]] => unit + -> tag ([Colspan = int, Rowspan = int] ++ tableAttrs) + ([Tr] ++ other) ([Body] ++ other) [] [] +val td : other ::: {Unit} -> [other ~ [Body, Tr]] => unit + -> tag ([Colspan = int, Rowspan = int] ++ tableAttrs) + ([Tr] ++ other) ([Body] ++ other) [] [] + +val thead : other ::: {Unit} -> [other ~ [Table]] => unit + -> tag tableAttrs + ([Table] ++ other) ([Table] ++ other) [] [] +val tbody : other ::: {Unit} -> [other ~ [Table]] => unit + -> tag tableAttrs + ([Table] ++ other) ([Table] ++ other) [] [] +val tfoot : other ::: {Unit} -> [other ~ [Table]] => unit + -> tag tableAttrs + ([Table] ++ other) ([Table] ++ other) [] [] + +(** Definition lists *) + +val dl : other ::: {Unit} -> [other ~ [Body,Dl]] + => unit + -> tag [Data = data_attr] ([Body] ++ other) ([Dl] ++ other) [] [] + +val dt : other ::: {Unit} -> [other ~ [Body,Dl]] + => unit + -> tag [Data = data_attr] ([Dl] ++ other) ([Body] ++ other) [] [] + +val dd : other ::: {Unit} -> [other ~ [Body,Dl]] + => unit + -> tag [Data = data_attr] ([Dl] ++ other) ([Body] ++ other) [] [] + + +(** Aborting *) + +val error : t ::: Type -> xbody -> t + +(* Client-side-only handlers: *) +val onError : (xbody -> transaction unit) -> transaction unit +val onFail : (string -> transaction unit) -> transaction unit +val onConnectFail : transaction unit -> transaction unit +val onDisconnect : transaction unit -> transaction unit +val onServerError : (string -> transaction unit) -> transaction unit + +(* More standard document-level JavaScript handlers *) +val onClick : (mouseEvent -> transaction unit) -> transaction unit +val onDblclick : (mouseEvent -> transaction unit) -> transaction unit +val onContextmenu : (mouseEvent -> transaction unit) -> transaction unit +val onKeydown : (keyEvent -> transaction unit) -> transaction unit +val onKeypress : (keyEvent -> transaction unit) -> transaction unit +val onKeyup : (keyEvent -> transaction unit) -> transaction unit +val onMousedown : (mouseEvent -> transaction unit) -> transaction unit +val onMouseenter : (mouseEvent -> transaction unit) -> transaction unit +val onMouseleave : (mouseEvent -> transaction unit) -> transaction unit +val onMousemove : (mouseEvent -> transaction unit) -> transaction unit +val onMouseout : (mouseEvent -> transaction unit) -> transaction unit +val onMouseover : (mouseEvent -> transaction unit) -> transaction unit +val onMouseup : (mouseEvent -> transaction unit) -> transaction unit + +(* Prevents default handling of current event *) +val preventDefault : transaction unit +(* Stops propagation of current event *) +val stopPropagation : transaction unit + +val show_xml : ctx ::: {Unit} -> use ::: {Type} -> bind ::: {Type} -> show (xml ctx use bind) + + +(** Tasks *) + +con task_kind :: Type -> Type +val initialize : task_kind unit +val clientLeaves : task_kind client +val periodic : int -> task_kind unit + + +(** Information flow security *) + +type sql_policy + +val sendClient : tables ::: {{Type}} -> exps ::: {Type} + -> [tables ~ exps] => sql_query [] [] tables exps + -> sql_policy + +val sendOwnIds : sql_sequence -> sql_policy + +val mayInsert : fs ::: {Type} -> tables ::: {{Type}} -> [[New] ~ tables] + => sql_query [] [] ([New = fs] ++ tables) [] + -> sql_policy + +val mayDelete : fs ::: {Type} -> tables ::: {{Type}} -> [[Old] ~ tables] + => sql_query [] [] ([Old = fs] ++ tables) [] + -> sql_policy + +val mayUpdate : fs ::: {Type} -> tables ::: {{Type}} -> [[Old, New] ~ tables] + => sql_query [] [] ([Old = fs, New = fs] ++ tables) [] + -> sql_policy + +val also : sql_policy -> sql_policy -> sql_policy + +val debug : string -> transaction unit +val naughtyDebug : string -> int + +val rand : transaction int diff --git a/lib/ur/char.ur b/lib/ur/char.ur new file mode 100644 index 0000000..d2890be --- /dev/null +++ b/lib/ur/char.ur @@ -0,0 +1,19 @@ +type t = char + +val isAlnum = Basis.isalnum +val isAlpha = Basis.isalpha +val isBlank = Basis.isblank +val isCntrl = Basis.iscntrl +val isDigit = Basis.isdigit +val isGraph = Basis.isgraph +val isLower = Basis.islower +val isPrint = Basis.isprint +val isPunct = Basis.ispunct +val isSpace = Basis.isspace +val isUpper = Basis.isupper +val isXdigit = Basis.isxdigit +val toLower = Basis.tolower +val toUpper = Basis.toupper + +val toInt = Basis.ord +val fromInt = Basis.chr diff --git a/lib/ur/char.urs b/lib/ur/char.urs new file mode 100644 index 0000000..c185af9 --- /dev/null +++ b/lib/ur/char.urs @@ -0,0 +1,19 @@ +type t = char + +val isAlnum : t -> bool +val isAlpha : t -> bool +val isBlank : t -> bool +val isCntrl : t -> bool +val isDigit : t -> bool +val isGraph : t -> bool +val isLower : t -> bool +val isPrint : t -> bool +val isPunct : t -> bool +val isSpace : t -> bool +val isUpper : t -> bool +val isXdigit : t -> bool +val toLower : t -> t +val toUpper : t -> t + +val toInt : t -> int +val fromInt : int -> t diff --git a/lib/ur/datetime.ur b/lib/ur/datetime.ur new file mode 100644 index 0000000..9aeab29 --- /dev/null +++ b/lib/ur/datetime.ur @@ -0,0 +1,135 @@ +datatype day_of_week = Sunday | Monday | Tuesday | Wednesday | Thursday | + Friday | Saturday + +val show_day_of_week = mkShow (fn dow => case dow of + Sunday => "Sunday" + | Monday => "Monday" + | Tuesday => "Tuesday" + | Wednesday => "Wednesday" + | Thursday => "Thursday" + | Friday => "Friday" + | Saturday => "Saturday") + +fun dayOfWeekToInt dow = case dow of + Sunday => 0 + | Monday => 1 + | Tuesday => 2 + | Wednesday => 3 + | Thursday => 4 + | Friday => 5 + | Saturday => 6 + +fun intToDayOfWeek i = case i of + 0 => Sunday + | 1 => Monday + | 2 => Tuesday + | 3 => Wednesday + | 4 => Thursday + | 5 => Friday + | 6 => Saturday + | n => error <xml>Invalid day of week {[n]}</xml> + +val eq_day_of_week = mkEq (fn a b => dayOfWeekToInt a = dayOfWeekToInt b) + + +datatype month = January | February | March | April | May | June | July | + August | September | October | November | December + +val show_month = mkShow (fn m => case m of + January => "January" + | February => "February" + | March => "March" + | April => "April" + | May => "May" + | June => "June" + | July => "July" + | August => "August" + | September => "September" + | October => "October" + | November => "November" + | December => "December") + +type t = { + Year : int, + Month : month, + Day : int, + Hour : int, + Minute : int, + Second : int +} + +fun monthToInt m = case m of + January => 0 + | February => 1 + | March => 2 + | April => 3 + | May => 4 + | June => 5 + | July => 6 + | August => 7 + | September => 8 + | October => 9 + | November => 10 + | December => 11 + +fun intToMonth i = case i of + 0 => January + | 1 => February + | 2 => March + | 3 => April + | 4 => May + | 5 => June + | 6 => July + | 7 => August + | 8 => September + | 9 => October + | 10 => November + | 11 => December + | n => error <xml>Invalid month number {[n]}</xml> + +val eq_month = mkEq (fn a b => monthToInt a = monthToInt b) + + +fun toTime dt : time = fromDatetime dt.Year (monthToInt dt.Month) dt.Day + dt.Hour dt.Minute dt.Second + +fun fromTime t : t = { + Year = datetimeYear t, + Month = intToMonth (datetimeMonth t), + Day = datetimeDay t, + Hour = datetimeHour t, + Minute = datetimeMinute t, + Second = datetimeSecond t +} + +val ord_datetime = mkOrd { Lt = fn a b => toTime a < toTime b, + Le = fn a b => toTime a <= toTime b } + +fun format fmt dt : string = timef fmt (toTime dt) + +fun dayOfWeek dt : day_of_week = intToDayOfWeek (datetimeDayOfWeek (toTime dt)) + +val now : transaction t = + n <- now; + return (fromTime n) + +(* Normalize a datetime. This will convert, e.g., January 32nd into February + 1st. *) + +fun normalize dt = fromTime (toTime dt) +fun addToField [nm :: Name] [rest ::: {Type}] [[nm] ~ rest] + (delta : int) (r : $([nm = int] ++ rest)) + : $([nm = int] ++ rest) = + (r -- nm) ++ {nm = r.nm + delta} + + +(* Functions for adding to a datetime. There is no addMonths or addYears since + it's not clear what should be done; what's 1 month after January 31, or 1 + year after February 29th? + + These can't all be defined in terms of addSeconds because of leap seconds. *) + +fun addSeconds n dt = normalize (addToField [#Second] n dt) +fun addMinutes n dt = normalize (addToField [#Minute] n dt) +fun addHours n dt = normalize (addToField [#Hour] n dt) +fun addDays n dt = normalize (addToField [#Day] n dt) diff --git a/lib/ur/datetime.urs b/lib/ur/datetime.urs new file mode 100644 index 0000000..972f86b --- /dev/null +++ b/lib/ur/datetime.urs @@ -0,0 +1,38 @@ +datatype day_of_week = Sunday | Monday | Tuesday | Wednesday | Thursday | + Friday | Saturday + +datatype month = January | February | March | April | May | June | July | + August | September | October | November | December + + +type t = { + Year : int, + Month : month, + Day : int, + Hour : int, + Minute : int, + Second : int +} + +val ord_datetime : ord t + +val show_day_of_week : show day_of_week +val show_month : show month +val eq_day_of_week : eq day_of_week +val eq_month : eq month +val dayOfWeekToInt : day_of_week -> int +val intToDayOfWeek : int -> day_of_week +val monthToInt : month -> int +val intToMonth : int -> month + +val toTime : t -> time +val fromTime : time -> t +val format : string -> t -> string +val dayOfWeek : t -> day_of_week +val now : transaction t +val normalize : t -> t + +val addSeconds : int -> t -> t +val addMinutes : int -> t -> t +val addHours : int -> t -> t +val addDays : int -> t -> t diff --git a/lib/ur/json.ur b/lib/ur/json.ur new file mode 100644 index 0000000..9288a6d --- /dev/null +++ b/lib/ur/json.ur @@ -0,0 +1,387 @@ +con json a = {ToJson : a -> string, + FromJson : string -> a * string} + +fun mkJson [a] (x : {ToJson : a -> string, + FromJson : string -> a * string}) = x + +fun skipSpaces s = + let + val len = String.length s + + fun skip i = + if i >= len then + "" + else + let + val ch = String.sub s i + in + if Char.isSpace ch then + skip (i+1) + else + String.substring s {Start = i, Len = len-i} + end + in + skip 0 + end + +fun toJson [a] (j : json a) : a -> string = j.ToJson +fun fromJson' [a] (j : json a) : string -> a * string = j.FromJson + +fun fromJson [a] (j : json a) (s : string) : a = + let + val (v, s') = j.FromJson (skipSpaces s) + in + if String.all Char.isSpace s' then + v + else + error <xml>Extra content at end of JSON record: {[s']}</xml> + end + +fun escape s = + let + fun esc s = + case s of + "" => "\"" + | _ => + let + val ch = String.sub s 0 + in + (if ch = #"\"" || ch = #"\\" then + "\\" ^ String.str ch + else + String.str ch) ^ esc (String.suffix s 1) + end + in + "\"" ^ esc s + end + +fun unescape s = + let + val len = String.length s + + fun findEnd i = + if i >= len then + error <xml>JSON unescape: string ends before quote: {[s]}</xml> + else + let + val ch = String.sub s i + in + case ch of + #"\"" => i + | #"\\" => + if i+1 >= len then + error <xml>JSON unescape: Bad escape sequence: {[s]}</xml> + else + findEnd (i+2) + | _ => findEnd (i+1) + end + + val last = findEnd 1 + + fun unesc i = + if i >= last then + "" + else + let + val ch = String.sub s i + in + case ch of + #"\\" => + if i+1 >= len then + error <xml>JSON unescape: Bad escape sequence: {[s]}</xml> + else + String.str (String.sub s (i+1)) ^ unesc (i+2) + | _ => String.str ch ^ unesc (i+1) + end + in + if len = 0 || String.sub s 0 <> #"\"" then + error <xml>JSON unescape: String doesn't start with double quote: {[s]}</xml> + else + (unesc 1, String.substring s {Start = last+1, Len = len-last-1}) + end + +val json_string = {ToJson = escape, + FromJson = unescape} + +fun numIn [a] (_ : read a) s : a * string = + let + val len = String.length s + + fun findEnd i = + if i >= len then + i + else + let + val ch = String.sub s i + in + if Char.isDigit ch || ch = #"-" || ch = #"." || ch = #"E" || ch = #"e" then + findEnd (i+1) + else + i + end + + val last = findEnd 0 + in + (readError (String.substring s {Start = 0, Len = last}), String.substring s {Start = last, Len = len-last}) + end + +fun json_num [a] (_ : show a) (_ : read a) : json a = {ToJson = show, + FromJson = numIn} + +val json_int = json_num +val json_float = json_num + +val json_bool = {ToJson = fn b => if b then "true" else "false", + FromJson = fn s => if String.isPrefix {Full = s, Prefix = "true"} then + (True, String.substring s {Start = 4, Len = String.length s - 4}) + else if String.isPrefix {Full = s, Prefix = "false"} then + (False, String.substring s {Start = 5, Len = String.length s - 5}) + else + error <xml>JSON: bad boolean string: {[s]}</xml>} + +fun json_option [a] (j : json a) : json (option a) = + {ToJson = fn v => case v of + None => "null" + | Some v => j.ToJson v, + FromJson = fn s => if String.isPrefix {Full = s, Prefix = "null"} then + (None, String.substring s {Start = 4, Len = String.length s - 4}) + else + let + val (v, s') = j.FromJson s + in + (Some v, s') + end} + +fun json_list [a] (j : json a) : json (list a) = + let + fun toJ' (ls : list a) : string = + case ls of + [] => "" + | x :: ls => "," ^ toJson j x ^ toJ' ls + + fun toJ (x : list a) : string = + case x of + [] => "[]" + | x :: [] => "[" ^ toJson j x ^ "]" + | x :: ls => "[" ^ toJson j x ^ toJ' ls ^ "]" + + fun fromJ (s : string) : list a * string = + let + fun fromJ' (s : string) : list a * string = + if String.length s = 0 then + error <xml>JSON list doesn't end with ']'</xml> + else + let + val ch = String.sub s 0 + in + case ch of + #"]" => ([], String.substring s {Start = 1, Len = String.length s - 1}) + | _ => + let + val (x, s') = j.FromJson s + val s' = skipSpaces s' + val s' = if String.length s' = 0 then + error <xml>JSON list doesn't end with ']'</xml> + else if String.sub s' 0 = #"," then + skipSpaces (String.substring s' {Start = 1, Len = String.length s' - 1}) + else + s' + + val (ls, s'') = fromJ' s' + in + (x :: ls, s'') + end + end + in + if String.length s = 0 || String.sub s 0 <> #"[" then + error <xml>JSON list doesn't start with '[': {[s]}</xml> + else + fromJ' (skipSpaces (String.substring s {Start = 1, Len = String.length s - 1})) + end + in + {ToJson = toJ, + FromJson = fromJ} + end + +fun skipOne s = + let + fun skipOne s dquote squote brace bracket = + if String.length s = 0 then + s + else + let + val ch = String.sub s 0 + val rest = String.suffix s 1 + in + case ch of + #"\"" => skipOne rest (not dquote) squote brace bracket + | #"'" => skipOne rest dquote (not squote) brace bracket + | #"\\" => if String.length s >= 2 then + skipOne (String.suffix s 2) dquote squote brace bracket + else + "" + | #"{" => skipOne rest dquote squote (brace + 1) bracket + | #"}" => if brace = 0 then + s + else + skipOne rest dquote squote (brace - 1) bracket + + | #"[" => skipOne rest dquote squote brace (bracket + 1) + | #"]" => + if bracket = 0 then + s + else + skipOne rest dquote squote brace (bracket - 1) + + | #"," => + if not dquote && not squote && brace = 0 && bracket = 0 then + s + else + skipOne rest dquote squote brace bracket + + | _ => skipOne rest dquote squote brace bracket + end + in + skipOne s False False 0 0 + end + +fun json_record [ts ::: {Type}] (fl : folder ts) (jss : $(map json ts)) (names : $(map (fn _ => string) ts)) : json $ts = + {ToJson = fn r => "{" ^ @foldR3 [json] [fn _ => string] [ident] [fn _ => string] + (fn [nm ::_] [t ::_] [r ::_] [[nm] ~ r] (j : json t) name v acc => + escape name ^ ":" ^ j.ToJson v ^ (case acc of + "" => "" + | acc => "," ^ acc)) + "" fl jss names r ^ "}", + FromJson = fn s => + let + fun fromJ s (r : $(map option ts)) : $(map option ts) * string = + if String.length s = 0 then + error <xml>JSON object doesn't end in brace</xml> + else if String.sub s 0 = #"}" then + (r, String.substring s {Start = 1, Len = String.length s - 1}) + else let + val (name, s') = unescape s + val s' = skipSpaces s' + val s' = if String.length s' = 0 || String.sub s' 0 <> #":" then + error <xml>No colon after JSON object field name</xml> + else + skipSpaces (String.substring s' {Start = 1, Len = String.length s' - 1}) + + val (r, s') = @foldR2 [json] [fn _ => string] [fn ts => $(map option ts) -> $(map option ts) * string] + (fn [nm ::_] [t ::_] [r ::_] [[nm] ~ r] (j : json t) name' acc r => + if name = name' then + let + val (v, s') = j.FromJson s' + in + (r -- nm ++ {nm = Some v}, s') + end + else + let + val (r', s') = acc (r -- nm) + in + (r' ++ {nm = r.nm}, s') + end) + (fn r => (r, skipOne s')) + fl jss names r + + val s' = skipSpaces s' + val s' = if String.length s' <> 0 && String.sub s' 0 = #"," then + skipSpaces (String.substring s' {Start = 1, Len = String.length s' - 1}) + else + s' + in + fromJ s' r + end + in + if String.length s = 0 || String.sub s 0 <> #"{" then + error <xml>JSON record doesn't begin with brace</xml> + else + let + val (r, s') = fromJ (skipSpaces (String.substring s {Start = 1, Len = String.length s - 1})) + (@map0 [option] (fn [t ::_] => None) fl) + in + (@map2 [option] [fn _ => string] [ident] (fn [t] (v : option t) name => + case v of + None => error <xml>Missing JSON object field {[name]}</xml> + | Some v => v) fl r names, s') + end + end} + +fun destrR [K] [f :: K -> Type] [fr :: K -> Type] [t ::: Type] + (f : p :: K -> f p -> fr p -> t) + [r ::: {K}] (fl : folder r) (v : variant (map f r)) (r : $(map fr r)) : t = + match v + (@Top.mp [fr] [fn p => f p -> t] + (fn [p] (m : fr p) (v : f p) => f [p] v m) + fl r) + +fun json_variant [ts ::: {Type}] (fl : folder ts) (jss : $(map json ts)) (names : $(map (fn _ => string) ts)) : json (variant ts) = + {ToJson = fn r => let val jnames = @map2 [json] [fn _ => string] [fn x => json x * string] + (fn [t] (j : json t) (name : string) => (j, name)) fl jss names + in @destrR [ident] [fn x => json x * string] + (fn [p ::_] (v : p) (j : json p, name : string) => + "{" ^ escape name ^ ":" ^ j.ToJson v ^ "}") fl r jnames + end, + FromJson = fn s => + if String.length s = 0 || String.sub s 0 <> #"{" then + error <xml>JSON variant doesn't begin with brace</xml> + else + let + val (name, s') = unescape (skipSpaces (String.suffix s 1)) + val s' = skipSpaces s' + val s' = if String.length s' = 0 || String.sub s' 0 <> #":" then + error <xml>No colon after JSON object field name</xml> + else + skipSpaces (String.substring s' {Start = 1, Len = String.length s' - 1}) + + val (r, s') = (@foldR2 [json] [fn _ => string] + [fn ts => ts' :: {Type} -> [ts ~ ts'] => variant (ts ++ ts') * string] + (fn [nm ::_] [t ::_] [rest ::_] [[nm] ~ rest] (j : json t) name' + (acc : ts' :: {Type} -> [rest ~ ts'] => variant (rest ++ ts') * string) [fwd ::_] [[nm = t] ++ rest ~ fwd] => + if name = name' + then + let val (v, s') = j.FromJson s' + in (make [nm] v, s') + end + else acc [fwd ++ [nm = t]] + ) + (fn [fwd ::_] [[] ~ fwd] => error <xml>Unknown JSON object variant name {[name]}</xml>) + fl jss names) [[]] ! + + val s' = skipSpaces s' + val s' = if String.length s' <> 0 && String.sub s' 0 = #"," then + skipSpaces (String.substring s' {Start = 1, Len = String.length s' - 1}) + else + s' + in + if String.length s' = 0 then + error <xml>JSON object doesn't end in brace</xml> + else if String.sub s' 0 = #"}" then + (r, String.substring s' {Start = 1, Len = String.length s' - 1}) + else error <xml>Junk after JSON value in object</xml> + end + } + +val json_unit : json unit = json_record {} {} + +functor Recursive (M : sig + con t :: Type -> Type + val json_t : a ::: Type -> json a -> json (t a) + end) = struct + open M + + datatype r = Rec of t r + + fun rTo (Rec x) = (json_t {ToJson = rTo, + FromJson = fn _ => error <xml>Tried to FromJson in ToJson!</xml>}).ToJson x + + fun rFrom s = + let + val (x, s') = (json_t {ToJson = fn _ => error <xml>Tried to ToJson in FromJson!</xml>, + FromJson = rFrom}).FromJson s + in + (Rec x, s') + end + + val json_r = {ToJson = rTo, FromJson = rFrom} +end diff --git a/lib/ur/json.urs b/lib/ur/json.urs new file mode 100644 index 0000000..b4bd635 --- /dev/null +++ b/lib/ur/json.urs @@ -0,0 +1,31 @@ +(** The JSON text-based serialization format *) + +class json + +val toJson : a ::: Type -> json a -> a -> string +val fromJson : a ::: Type -> json a -> string -> a +val fromJson' : a ::: Type -> json a -> string -> a * string + +val mkJson : a ::: Type -> {ToJson : a -> string, + FromJson : string -> a * string} -> json a + +val json_string : json string +val json_int : json int +val json_float : json float +val json_bool : json bool +val json_option : a ::: Type -> json a -> json (option a) +val json_list : a ::: Type -> json a -> json (list a) + +val json_record : ts ::: {Type} -> folder ts -> $(map json ts) -> $(map (fn _ => string) ts) -> json $ts +val json_variant : ts ::: {Type} -> folder ts -> $(map json ts) -> $(map (fn _ => string) ts) -> json (variant ts) + +val json_unit : json unit + +functor Recursive (M : sig + con t :: Type -> Type + val json_t : a ::: Type -> json a -> json (t a) + end) : sig + datatype r = Rec of M.t r + + val json_r : json r +end diff --git a/lib/ur/list.ur b/lib/ur/list.ur new file mode 100644 index 0000000..cc53367 --- /dev/null +++ b/lib/ur/list.ur @@ -0,0 +1,498 @@ +datatype t = datatype Basis.list + +val show = fn [a] (_ : show a) => + let + fun show' (ls : list a) = + case ls of + [] => "[]" + | x :: ls => show x ^ " :: " ^ show' ls + in + mkShow show' + end + +val eq = fn [a] (_ : eq a) => + let + fun eq' (ls1 : list a) ls2 = + case (ls1, ls2) of + ([], []) => True + | (x1 :: ls1, x2 :: ls2) => x1 = x2 && eq' ls1 ls2 + | _ => False + in + mkEq eq' + end + +fun foldl [a] [b] (f : a -> b -> b) = + let + fun foldl' acc ls = + case ls of + [] => acc + | x :: ls => foldl' (f x acc) ls + in + foldl' + end + +val rev = fn [a] => + let + fun rev' acc (ls : list a) = + case ls of + [] => acc + | x :: ls => rev' (x :: acc) ls + in + rev' [] + end + +fun foldr [a] [b] f (acc : b) (ls : list a) = foldl f acc (rev ls) + +fun foldlAbort [a] [b] f = + let + fun foldlAbort' acc ls = + case ls of + [] => Some acc + | x :: ls => + case f x acc of + None => None + | Some acc' => foldlAbort' acc' ls + in + foldlAbort' + end + +val length = fn [a] => + let + fun length' acc (ls : list a) = + case ls of + [] => acc + | _ :: ls => length' (acc + 1) ls + in + length' 0 + end + +fun foldlMapAbort [a] [b] [c] f = + let + fun foldlMapAbort' ls' acc ls = + case ls of + [] => Some (rev ls', acc) + | x :: ls => + case f x acc of + None => None + | Some (x', acc') => foldlMapAbort' (x' :: ls') acc' ls + in + foldlMapAbort' [] + end + +val revAppend = fn [a] => + let + fun ra (ls : list a) acc = + case ls of + [] => acc + | x :: ls => ra ls (x :: acc) + in + ra + end + +fun append [a] (ls1 : t a) (ls2 : t a) = revAppend (rev ls1) ls2 + +fun mp [a] [b] f = + let + fun mp' acc ls = + case ls of + [] => rev acc + | x :: ls => mp' (f x :: acc) ls + in + mp' [] + end + +fun mapi [a] [b] f = + let + fun mp' n acc ls = + case ls of + [] => rev acc + | x :: ls => mp' (n + 1) (f n x :: acc) ls + in + mp' 0 [] + end + +fun mapPartial [a] [b] f = + let + fun mp' acc ls = + case ls of + [] => rev acc + | x :: ls => mp' (case f x of + None => acc + | Some y => y :: acc) ls + in + mp' [] + end + +fun mapX [a] [ctx ::: {Unit}] f = + let + fun mapX' ls = + case ls of + [] => <xml/> + | x :: ls => <xml>{f x}{mapX' ls}</xml> + in + mapX' + end + +fun mapXi [a] [ctx ::: {Unit}] f = + let + fun mapX' i ls = + case ls of + [] => <xml/> + | x :: ls => <xml>{f i x}{mapX' (i + 1) ls}</xml> + in + mapX' 0 + end + +fun mapM [m ::: (Type -> Type)] (_ : monad m) [a] [b] f = + let + fun mapM' acc ls = + case ls of + [] => return (rev acc) + | x :: ls => x' <- f x; mapM' (x' :: acc) ls + in + mapM' [] + end + +fun mapPartialM [m ::: (Type -> Type)] (_ : monad m) [a] [b] f = + let + fun mapPartialM' acc ls = + case ls of + [] => return (rev acc) + | x :: ls => + v <- f x; + mapPartialM' (case v of + None => acc + | Some x' => x' :: acc) ls + in + mapPartialM' [] + end + +fun mapXM [m ::: (Type -> Type)] (_ : monad m) [a] [ctx ::: {Unit}] f = + let + fun mapXM' ls = + case ls of + [] => return <xml/> + | x :: ls => + this <- f x; + rest <- mapXM' ls; + return <xml>{this}{rest}</xml> + in + mapXM' + end + +fun filter [a] f = + let + fun fil acc ls = + case ls of + [] => rev acc + | x :: ls => fil (if f x then x :: acc else acc) ls + in + fil [] + end + +fun exists [a] f = + let + fun ex ls = + case ls of + [] => False + | x :: ls => + if f x then + True + else + ex ls + in + ex + end + +fun foldlMap [a] [b] [c] f = + let + fun fold ls' st ls = + case ls of + [] => (rev ls', st) + | x :: ls => + case f x st of + (y, st) => fold (y :: ls') st ls + in + fold [] + end + +fun mem [a] (_ : eq a) (x : a) = + let + fun mm ls = + case ls of + [] => False + | y :: ls => y = x || mm ls + in + mm + end + +fun find [a] f = + let + fun find' ls = + case ls of + [] => None + | x :: ls => + if f x then + Some x + else + find' ls + in + find' + end + +fun search [a] [b] f = + let + fun search' ls = + case ls of + [] => None + | x :: ls => + case f x of + None => search' ls + | v => v + in + search' + end + +fun foldlM [m] (_ : monad m) [a] [b] f = + let + fun foldlM' acc ls = + case ls of + [] => return acc + | x :: ls => + acc <- f x acc; + foldlM' acc ls + in + foldlM' + end + +fun foldlMi [m] (_ : monad m) [a] [b] f = + let + fun foldlMi' i acc ls = + case ls of + [] => return acc + | x :: ls => + acc <- f i x acc; + foldlMi' (i + 1) acc ls + in + foldlMi' 0 + end + +fun filterM [m] (_ : monad m) [a] (p : a -> m bool) = + let + fun filterM' (acc : list a) (xs : list a) : m (list a) = + case xs of + [] => return (rev acc) + | x :: xs => + c <- p x; + filterM' (if c then x :: acc else acc) xs + in + filterM' [] + end + +fun all [m] f = + let + fun all' ls = + case ls of + [] => True + | x :: ls => f x && all' ls + in + all' + end + +fun app [m] (_ : monad m) [a] f = + let + fun app' ls = + case ls of + [] => return () + | x :: ls => + f x; + app' ls + in + app' + end + +fun appi [m] (_ : monad m) [a] f = + let + fun app' i ls = + case ls of + [] => return () + | x :: ls => + f i x; + app' (i + 1) ls + in + app' 0 + end + +fun mapQuery [tables ::: {{Type}}] [exps ::: {Type}] [t ::: Type] + [tables ~ exps] (q : sql_query [] [] tables exps) + (f : $(exps ++ map (fn fields :: {Type} => $fields) tables) -> t) = + ls <- query q + (fn fs acc => return (f fs :: acc)) + []; + return (rev ls) + +fun mapQueryM [tables ::: {{Type}}] [exps ::: {Type}] [t ::: Type] + [tables ~ exps] (q : sql_query [] [] tables exps) + (f : $(exps ++ map (fn fields :: {Type} => $fields) tables) -> transaction t) = + ls <- query q + (fn fs acc => v <- f fs; return (v :: acc)) + []; + return (rev ls) + +fun mapQueryPartialM [tables ::: {{Type}}] [exps ::: {Type}] [t ::: Type] + [tables ~ exps] (q : sql_query [] [] tables exps) + (f : $(exps ++ map (fn fields :: {Type} => $fields) tables) -> transaction (option t)) = + ls <- query q + (fn fs acc => v <- f fs; + return (case v of + None => acc + | Some v => v :: acc)) + []; + return (rev ls) + +fun sort [a] (gt : a -> a -> bool) (ls : t a) : t a = + let + fun split ls acc1 acc2 = + case ls of + [] => (rev acc1, rev acc2) + | x :: [] => (rev (x :: acc1), rev acc2) + | x1 :: x2 :: ls' => split ls' (x1 :: acc1) (x2 :: acc2) + + fun merge ls1 ls2 acc = + case (ls1, ls2) of + ([], _) => revAppend acc ls2 + | (_, []) => revAppend acc ls1 + | (x1 :: ls1', x2 :: ls2') => if gt x1 x2 then merge ls1 ls2' (x2 :: acc) else merge ls1' ls2 (x1 :: acc) + + fun sort' ls = + case ls of + [] => ls + | _ :: [] => ls + | _ => + let + val (ls1, ls2) = split ls [] [] + in + merge (sort' ls1) (sort' ls2) [] + end + in + sort' ls + end + +val nth [a] = + let + fun nth (ls : list a) (n : int) : option a = + case ls of + [] => None + | x :: ls' => + if n <= 0 then + Some x + else + nth ls' (n-1) + in + nth + end + +fun replaceNth [a] (ls : list a) (n : int) (v : a) : list a = + let + fun repNth (ls : list a) (n : int) (acc : list a) = + case ls of + [] => rev acc + | x :: ls' => if n <= 0 then + revAppend acc (v :: ls') + else + repNth ls' (n-1) (x :: acc) + in + repNth ls n [] + end + +fun assoc [a] [b] (_ : eq a) (x : a) = + let + fun assoc' (ls : list (a * b)) = + case ls of + [] => None + | (y, z) :: ls => + if x = y then + Some z + else + assoc' ls + in + assoc' + end + +fun assocAdd [a] [b] (_ : eq a) (x : a) (y : b) (ls : t (a * b)) = + case assoc x ls of + None => (x, y) :: ls + | Some _ => ls + +fun recToList [a ::: Type] [r ::: {Unit}] (fl : folder r) + = @foldUR [a] [fn _ => list a] (fn [nm ::_] [rest ::_] [[nm] ~ rest] x xs => + x :: xs) [] fl + +fun take [a] (n : int) (xs : list a) : list a = + if n <= 0 then + [] + else + case xs of + [] => [] + | x :: xs => x :: take (n-1) xs + +fun drop [a] (n : int) (xs : list a) : list a = + if n <= 0 then + xs + else + case xs of + [] => [] + | x :: xs => drop (n-1) xs + +fun splitAt [a] (n : int) (xs : list a) : list a * list a = + (take n xs, drop n xs) + +fun span [a] (f : a -> bool) (ls : list a) : list a * list a = + let + fun span' ls acc = + case ls of + [] => (rev acc, []) + | x :: xs => if f x then span' xs (x :: acc) else (rev acc, ls) + in + span' ls [] + end + +fun groupBy [a] (f : a -> a -> bool) (ls : list a) : list (list a) = + let + fun groupBy' ls acc = + case ls of + [] => rev ([] :: acc) + | x :: xs => + let + val (ys, zs) = span (f x) xs + in + groupBy' zs ((x :: ys) :: acc) + end + in + groupBy' ls [] + end + +fun mapXiM [m ::: Type -> Type] (_ : monad m) [a] [ctx ::: {Unit}] (f : int -> a -> m (xml ctx [] [])) : t a -> m (xml ctx [] []) = + let + fun mapXiM' i ls = + case ls of + [] => return <xml/> + | x :: ls => + this <- f i x; + rest <- mapXiM' (i+1) ls; + return <xml>{this}{rest}</xml> + in + mapXiM' 0 + end + +fun tabulateM [m] (_ : monad m) [a] (f : int -> m a) n = + let + fun tabulate' n acc = + if n <= 0 then + return acc + else + (v <- f (n-1); + tabulate' (n-1) (v :: acc)) + in + tabulate' n [] + end diff --git a/lib/ur/list.urs b/lib/ur/list.urs new file mode 100644 index 0000000..fd56679 --- /dev/null +++ b/lib/ur/list.urs @@ -0,0 +1,116 @@ +datatype t = datatype Basis.list + +val show : a ::: Type -> show a -> show (t a) +val eq : a ::: Type -> eq a -> eq (t a) + +val foldl : a ::: Type -> b ::: Type -> (a -> b -> b) -> b -> t a -> b +val foldlAbort : a ::: Type -> b ::: Type -> (a -> b -> option b) -> b -> t a -> option b +val foldlMapAbort : a ::: Type -> b ::: Type -> c ::: Type + -> (a -> b -> option (c * b)) -> b -> t a -> option (t c * b) + +val foldr : a ::: Type -> b ::: Type -> (a -> b -> b) -> b -> t a -> b + +val length : a ::: Type -> t a -> int + +val rev : a ::: Type -> t a -> t a + +val revAppend : a ::: Type -> t a -> t a -> t a + +val append : a ::: Type -> t a -> t a -> t a + +val mp : a ::: Type -> b ::: Type -> (a -> b) -> t a -> t b + +val mapPartial : a ::: Type -> b ::: Type -> (a -> option b) -> t a -> t b + +val mapi : a ::: Type -> b ::: Type -> (int -> a -> b) -> t a -> t b + +val mapX : a ::: Type -> ctx ::: {Unit} -> (a -> xml ctx [] []) -> t a -> xml ctx [] [] + +val mapXi : a ::: Type -> ctx ::: {Unit} -> (int -> a -> xml ctx [] []) -> t a -> xml ctx [] [] + +val mapM : m ::: (Type -> Type) -> monad m -> a ::: Type -> b ::: Type + -> (a -> m b) -> t a -> m (t b) + +val mapPartialM : m ::: (Type -> Type) -> monad m -> a ::: Type -> b ::: Type -> (a -> m (option b)) -> t a -> m (t b) + +val mapXM : m ::: (Type -> Type) -> monad m -> a ::: Type -> ctx ::: {Unit} + -> (a -> m (xml ctx [] [])) -> t a -> m (xml ctx [] []) + +val mapXiM : m ::: (Type -> Type) -> monad m -> a ::: Type -> ctx ::: {Unit} -> (int -> a -> m (xml ctx [] [])) -> t a -> m (xml ctx [] []) + +val filter : a ::: Type -> (a -> bool) -> t a -> t a + +val exists : a ::: Type -> (a -> bool) -> t a -> bool + +val foldlM : m ::: (Type -> Type) -> monad m -> a ::: Type -> b ::: Type + -> (a -> b -> m b) -> b -> t a -> m b + +val foldlMi : m ::: (Type -> Type) -> monad m -> a ::: Type -> b ::: Type + -> (int -> a -> b -> m b) -> b -> t a -> m b + +val filterM : m ::: (Type -> Type) -> monad m -> a ::: Type + -> (a -> m bool) -> t a -> m (t a) + +val foldlMap : a ::: Type -> b ::: Type -> c ::: Type + -> (a -> b -> c * b) -> b -> t a -> t c * b + +val mem : a ::: Type -> eq a -> a -> t a -> bool + +val find : a ::: Type -> (a -> bool) -> t a -> option a + +val search : a ::: Type -> b ::: Type -> (a -> option b) -> t a -> option b + +val all : a ::: Type -> (a -> bool) -> t a -> bool + +val app : m ::: (Type -> Type) -> monad m -> a ::: Type + -> (a -> m unit) -> t a -> m unit + +val appi : m ::: (Type -> Type) -> monad m -> a ::: Type + -> (int -> a -> m unit) -> t a -> m unit + +val tabulateM : m ::: (Type -> Type) -> monad m -> a ::: Type + -> (int -> m a) -> int -> m (t a) + +val mapQuery : tables ::: {{Type}} -> exps ::: {Type} -> t ::: Type + -> [tables ~ exps] => + sql_query [] [] tables exps + -> ($(exps ++ map (fn fields :: {Type} => $fields) tables) -> t) + -> transaction (list t) + +val mapQueryM : tables ::: {{Type}} -> exps ::: {Type} -> t ::: Type + -> [tables ~ exps] => + sql_query [] [] tables exps + -> ($(exps ++ map (fn fields :: {Type} => $fields) tables) -> transaction t) + -> transaction (list t) + +val mapQueryPartialM : tables ::: {{Type}} -> exps ::: {Type} -> t ::: Type + -> [tables ~ exps] => + sql_query [] [] tables exps + -> ($(exps ++ map (fn fields :: {Type} => $fields) tables) -> transaction (option t)) + -> transaction (list t) + +val sort : a ::: Type -> (a -> a -> bool) (* > predicate *) -> t a -> t a + +val nth : a ::: Type -> list a -> int -> option a +val replaceNth : a ::: Type -> list a -> int -> a -> list a + +(** Association lists *) + +val assoc : a ::: Type -> b ::: Type -> eq a -> a -> t (a * b) -> option b + +val assocAdd : a ::: Type -> b ::: Type -> eq a -> a -> b -> t (a * b) -> t (a * b) + +(** Converting records to lists *) + +val recToList : a ::: Type -> r ::: {Unit} -> folder r -> $(mapU a r) -> t a + +(* Divide a list into two sections at a particular 0-based position, returning the second, first, or both parts, respectively. *) +val drop : t ::: Type -> int -> list t -> list t +val take : t ::: Type -> int -> list t -> list t +val splitAt : t ::: Type -> int -> list t -> list t * list t + +(** Longest prefix of elements that satisfy a predicate, returned along with the remaining suffix *) +val span : a ::: Type -> (a -> bool) -> t a -> t a * t a + +(** Group a list into maximal adjacent segments where all elements compare as equal, according to the provided predicate. *) +val groupBy : a ::: Type -> (a -> a -> bool) -> t a -> t (t a) diff --git a/lib/ur/listPair.ur b/lib/ur/listPair.ur new file mode 100644 index 0000000..94b9287 --- /dev/null +++ b/lib/ur/listPair.ur @@ -0,0 +1,46 @@ +fun foldlAbort [a] [b] [c] f = + let + fun foldlAbort' acc ls1 ls2 = + case (ls1, ls2) of + ([], []) => Some acc + | (x1 :: ls1, x2 :: ls2) => + (case f x1 x2 acc of + None => None + | Some acc' => foldlAbort' acc' ls1 ls2) + | _ => None + in + foldlAbort' + end + +fun mapX [a] [b] [ctx ::: {Unit}] f = + let + fun mapX' ls1 ls2 = + case (ls1, ls2) of + ([], []) => <xml/> + | (x1 :: ls1, x2 :: ls2) => <xml>{f x1 x2}{mapX' ls1 ls2}</xml> + | _ => error <xml>ListPair.mapX: Unequal list lengths</xml> + in + mapX' + end + +fun all [a] [b] f = + let + fun all' ls1 ls2 = + case (ls1, ls2) of + ([], []) => True + | (x1 :: ls1, x2 :: ls2) => f x1 x2 && all' ls1 ls2 + | _ => False + in + all' + end + +fun mp [a] [b] [c] (f : a -> b -> c) = + let + fun map' ls1 ls2 = + case (ls1, ls2) of + ([], []) => [] + | (x1 :: ls1, x2 :: ls2) => f x1 x2 :: map' ls1 ls2 + | _ => error <xml>ListPair.map2: Unequal list lengths</xml> + in + map' + end diff --git a/lib/ur/listPair.urs b/lib/ur/listPair.urs new file mode 100644 index 0000000..b473e22 --- /dev/null +++ b/lib/ur/listPair.urs @@ -0,0 +1,10 @@ +val foldlAbort : a ::: Type -> b ::: Type -> c ::: Type + -> (a -> b -> c -> option c) -> c -> list a -> list b -> option c + +val mapX : a ::: Type -> b ::: Type -> ctx ::: {Unit} + -> (a -> b -> xml ctx [] []) -> list a -> list b -> xml ctx [] [] + +val all : a ::: Type -> b ::: Type -> (a -> b -> bool) -> list a -> list b -> bool + +val mp : a ::: Type -> b ::: Type -> c ::: Type + -> (a -> b -> c) -> list a -> list b -> list c diff --git a/lib/ur/monad.ur b/lib/ur/monad.ur new file mode 100644 index 0000000..8bff013 --- /dev/null +++ b/lib/ur/monad.ur @@ -0,0 +1,140 @@ +fun exec [m ::: Type -> Type] (_ : monad m) [ts ::: {Type}] r (fd : folder ts) = + @foldR [m] [fn ts => m $ts] + (fn [nm :: Name] [v :: Type] [rest :: {Type}] [[nm] ~ rest] action acc => + this <- action; + others <- acc; + return ({nm = this} ++ others)) + (return {}) fd r + +fun ignore [m ::: Type -> Type] (_ : monad m) [t] (v : m t) = x <- v; return () + +fun mp [m] (_ : monad m) [a] [b] f m = + v <- m; + return (f v) + +val liftM = @@mp + +fun foldR [K] [m] (_ : monad m) [tf :: K -> Type] [tr :: {K} -> Type] + (f : nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf t -> tr rest -> m (tr ([nm = t] ++ rest))) + (i : tr []) [r ::: {K}] (fl : folder r) = + @Top.fold [fn r :: {K} => $(map tf r) -> m (tr r)] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] + (acc : _ -> m (tr rest)) r => + acc' <- acc (r -- nm); + f [nm] [t] [rest] r.nm acc') + (fn _ => return i) + fl + +fun foldR2 [K] [m] (_ : monad m) [tf1 :: K -> Type] [tf2 :: K -> Type] [tr :: {K} -> Type] + (f : nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> tr rest -> m (tr ([nm = t] ++ rest))) + (i : tr []) [r ::: {K}] (fl : folder r) = + @Top.fold [fn r :: {K} => $(map tf1 r) -> $(map tf2 r) -> m (tr r)] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] + (acc : _ -> _ -> m (tr rest)) r1 r2 => + acc' <- acc (r1 -- nm) (r2 -- nm); + f [nm] [t] [rest] r1.nm r2.nm acc') + (fn _ _ => return i) + fl + +fun foldR3 [K] [m] (_ : monad m) [tf1 :: K -> Type] [tf2 :: K -> Type] [tf3 :: K -> Type] [tr :: {K} -> Type] + (f : nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> tf3 t -> tr rest -> m (tr ([nm = t] ++ rest))) + (i : tr []) [r ::: {K}] (fl : folder r) = + @Top.fold [fn r :: {K} => $(map tf1 r) -> $(map tf2 r) -> $(map tf3 r) -> m (tr r)] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] + (acc : _ -> _ -> _ -> m (tr rest)) r1 r2 r3 => + acc' <- acc (r1 -- nm) (r2 -- nm) (r3 -- nm); + f [nm] [t] [rest] r1.nm r2.nm r3.nm acc') + (fn _ _ _ => return i) + fl + +fun mapR0 [K] [m] (_ : monad m) [tr :: K -> Type] + (f : nm :: Name -> t :: K -> m (tr t)) [r ::: {K}] (fl : folder r) = + @Top.fold [fn r => m ($(map tr r))] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] (acc : m ($(map tr rest))) => + v <- f [nm] [t]; + vs <- acc; + return (vs ++ {nm = v})) + (return {}) + fl + +fun mapR [K] [m] (_ : monad m) [tf :: K -> Type] [tr :: K -> Type] + (f : nm :: Name -> t :: K -> tf t -> m (tr t)) = + @@foldR [m] _ [tf] [fn r => $(map tr r)] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] (v : tf t) + (acc : $(map tr rest)) => + v' <- f [nm] [t] v; + return (acc ++ {nm = v'})) + {} + +fun mapR2 [K] [m] (_ : monad m) [tf1 :: K -> Type] [tf2 :: K -> Type] [tr :: K -> Type] + (f : nm :: Name -> t :: K -> tf1 t -> tf2 t -> m (tr t)) = + @@foldR2 [m] _ [tf1] [tf2] [fn r => $(map tr r)] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] (v1 : tf1 t) (v2 : tf2 t) + (acc : $(map tr rest)) => + v' <- f [nm] [t] v1 v2; + return (acc ++ {nm = v'})) + {} + +fun mapR3 [K] [m] (_ : monad m) [tf1 :: K -> Type] [tf2 :: K -> Type] [tf3 :: K -> Type] [tr :: K -> Type] + (f : nm :: Name -> t :: K -> tf1 t -> tf2 t -> tf3 t -> m (tr t)) = + @@foldR3 [m] _ [tf1] [tf2] [tf3] [fn r => $(map tr r)] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] (v1 : tf1 t) (v2 : tf2 t) (v3 : tf3 t) + (acc : $(map tr rest)) => + v' <- f [nm] [t] v1 v2 v3; + return (acc ++ {nm = v'})) + {} + +fun foldMapR [K] [m] (_ : monad m) [tf :: K -> Type] [tf' :: K -> Type] [tr :: {K} -> Type] + (f : nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf t -> tr rest -> m (tf' t * tr ([nm = t] ++ rest))) + (i : tr []) [r ::: {K}] (fl : folder r) = + @Top.fold [fn r :: {K} => $(map tf r) -> m ($(map tf' r) * tr r)] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] + (acc : _ -> m ($(map tf' rest) * tr rest)) r => + p <- acc (r -- nm); + p' <- f [nm] [t] [rest] r.nm p.2; + return ({nm = p'.1} ++ p.1, p'.2)) + (fn _ => return ({}, i)) + fl + +fun appR [K] [m] (_ : monad m) [tf :: K -> Type] + (f : nm :: Name -> t :: K -> tf t -> m unit) + [r ::: {K}] (fl : folder r) = + @Top.fold [fn r :: {K} => $(map tf r) -> m unit] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] acc r => + acc (r -- nm); + f [nm] [t] r.nm) + (fn _ => return ()) + fl + +fun appR2 [K] [m] (_ : monad m) [tf1 :: K -> Type] [tf2 :: K -> Type] + (f : nm :: Name -> t :: K -> tf1 t -> tf2 t -> m unit) + [r ::: {K}] (fl : folder r) = + @Top.fold [fn r :: {K} => $(map tf1 r) -> $(map tf2 r) -> m unit] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] acc r1 r2 => + acc (r1 -- nm) (r2 -- nm); + f [nm] [t] r1.nm r2.nm) + (fn _ _ => return ()) + fl + +fun appR3 [K] [m] (_ : monad m) [tf1 :: K -> Type] [tf2 :: K -> Type] [tf3 :: K -> Type] + (f : nm :: Name -> t :: K -> tf1 t -> tf2 t -> tf3 t -> m unit) + [r ::: {K}] (fl : folder r) = + @Top.fold [fn r :: {K} => $(map tf1 r) -> $(map tf2 r) -> $(map tf3 r) -> m unit] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] acc r1 r2 r3 => + acc (r1 -- nm) (r2 -- nm) (r3 -- nm); + f [nm] [t] r1.nm r2.nm r3.nm) + (fn _ _ _ => return ()) + fl + +fun liftM2 [m ::: Type -> Type] (_ : monad m) [a] [b] [c] (f : a -> b -> c) (mx : m a) (my : m b) : m c = + x <- mx; + y <- my; + return (f x y) diff --git a/lib/ur/monad.urs b/lib/ur/monad.urs new file mode 100644 index 0000000..8ca8d0a --- /dev/null +++ b/lib/ur/monad.urs @@ -0,0 +1,90 @@ +val exec : m ::: (Type -> Type) -> monad m -> ts ::: {Type} + -> $(map m ts) -> folder ts -> m $ts + +val ignore : m ::: (Type -> Type) -> monad m -> t ::: Type + -> m t -> m unit + +val mp : m ::: (Type -> Type) -> monad m -> a ::: Type -> b ::: Type + -> (a -> b) -> m a -> m b + +val liftM : m ::: (Type -> Type) -> monad m -> a ::: Type -> b ::: Type + -> (a -> b) -> m a -> m b +(* Haskell-style synonym for [mp] *) + +val liftM2 : m ::: (Type -> Type) -> monad m -> a ::: Type -> b ::: Type -> c ::: Type + -> (a -> b -> c) -> m a -> m b -> m c + +val foldR : K --> m ::: (Type -> Type) -> monad m + -> tf :: (K -> Type) + -> tr :: ({K} -> Type) + -> (nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf t -> tr rest -> m (tr ([nm = t] ++ rest))) + -> tr [] + -> r ::: {K} -> folder r -> $(map tf r) -> m (tr r) + +val foldR2 : K --> m ::: (Type -> Type) -> monad m + -> tf1 :: (K -> Type) -> tf2 :: (K -> Type) + -> tr :: ({K} -> Type) + -> (nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> tr rest -> m (tr ([nm = t] ++ rest))) + -> tr [] + -> r ::: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r) -> m (tr r) + +val foldR3 : K --> m ::: (Type -> Type) -> monad m + -> tf1 :: (K -> Type) -> tf2 :: (K -> Type) -> tf3 :: (K -> Type) + -> tr :: ({K} -> Type) + -> (nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> tf3 t -> tr rest -> m (tr ([nm = t] ++ rest))) + -> tr [] + -> r ::: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r) -> $(map tf3 r) -> m (tr r) + +val mapR0 : K --> m ::: (Type -> Type) -> monad m + -> tr :: (K -> Type) + -> (nm :: Name -> t :: K -> m (tr t)) + -> r ::: {K} -> folder r -> m ($(map tr r)) + +val mapR : K --> m ::: (Type -> Type) -> monad m + -> tf :: (K -> Type) + -> tr :: (K -> Type) + -> (nm :: Name -> t :: K -> tf t -> m (tr t)) + -> r ::: {K} -> folder r -> $(map tf r) -> m ($(map tr r)) + +val mapR2 : K --> m ::: (Type -> Type) -> monad m + -> tf1 :: (K -> Type) -> tf2 :: (K -> Type) + -> tr :: (K -> Type) + -> (nm :: Name -> t :: K -> tf1 t -> tf2 t -> m (tr t)) + -> r ::: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r) -> m ($(map tr r)) + +val mapR3 : K --> m ::: (Type -> Type) -> monad m + -> tf1 :: (K -> Type) -> tf2 :: (K -> Type) -> tf3 :: (K -> Type) + -> tr :: (K -> Type) + -> (nm :: Name -> t :: K -> tf1 t -> tf2 t -> tf3 t -> m (tr t)) + -> r ::: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r) -> $(map tf3 r) -> m ($(map tr r)) + +val foldMapR : K --> m ::: (Type -> Type) -> monad m + -> tf :: (K -> Type) + -> tf' :: (K -> Type) + -> tr :: ({K} -> Type) + -> (nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf t -> tr rest -> m (tf' t * tr ([nm = t] ++ rest))) + -> tr [] + -> r ::: {K} -> folder r -> $(map tf r) -> m ($(map tf' r) * tr r) + +val appR : K --> m ::: (Type -> Type) -> monad m + -> tf :: (K -> Type) + -> (nm :: Name -> t :: K -> tf t -> m unit) + -> r ::: {K} -> folder r -> $(map tf r) -> m unit + +val appR2 : K --> m ::: (Type -> Type) -> monad m + -> tf1 :: (K -> Type) -> tf2 :: (K -> Type) + -> (nm :: Name -> t :: K -> tf1 t -> tf2 t -> m unit) + -> r ::: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r) -> m unit + +val appR3 : K --> m ::: (Type -> Type) -> monad m + -> tf1 :: (K -> Type) -> tf2 :: (K -> Type) -> tf3 :: (K -> Type) + -> (nm :: Name -> t :: K -> tf1 t -> tf2 t -> tf3 t -> m unit) + -> r ::: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r) -> $(map tf3 r) -> m unit diff --git a/lib/ur/option.ur b/lib/ur/option.ur new file mode 100644 index 0000000..baa0846 --- /dev/null +++ b/lib/ur/option.ur @@ -0,0 +1,61 @@ +datatype t = datatype Basis.option + +val monad = mkMonad {Return = @@Some, + Bind = fn [a] [b] (m1 : t a) (m2 : a -> t b) => + case m1 of + None => None + | Some v => m2 v} + +fun eq [a] (_ : eq a) = + mkEq (fn x y => + case (x, y) of + (None, None) => True + | (Some x, Some y) => x = y + | _ => False) + +fun ord [a] (_ : ord a) = + mkOrd {Lt = fn x y => + case (x, y) of + (None, Some _) => True + | (Some x, Some y) => x < y + | _ => False, + Le = fn x y => + case (x, y) of + (None, _) => True + | (Some x, Some y) => x <= y + | _ => False} + +fun isNone [a] x = + case x of + None => True + | Some _ => False + +fun isSome [a] x = + case x of + None => False + | Some _ => True + +fun mp [a] [b] f x = + case x of + None => None + | Some y => Some (f y) + +fun app [m] [a] (_ : monad m) (f : a -> m {}) x = + case x of + None => return () + | Some y => f y + +fun bind [a] [b] f x = + case x of + None => None + | Some y => f y + +fun get [a] (x : a) (o : option a) = + case o of + None => x + | Some v => v + +fun unsafeGet [a] (o : option a) = + case o of + None => error <xml>Option.unsafeGet: encountered None</xml> + | Some v => v diff --git a/lib/ur/option.urs b/lib/ur/option.urs new file mode 100644 index 0000000..c30c40e --- /dev/null +++ b/lib/ur/option.urs @@ -0,0 +1,16 @@ +datatype t = datatype Basis.option + +val monad : monad t + +val eq : a ::: Type -> eq a -> eq (t a) +val ord : a ::: Type -> ord a -> ord (t a) + +val isNone : a ::: Type -> t a -> bool +val isSome : a ::: Type -> t a -> bool + +val mp : a ::: Type -> b ::: Type -> (a -> b) -> t a -> t b +val app : m ::: (Type -> Type) -> a ::: Type -> monad m -> (a -> m {}) -> t a -> m {} +val bind : a ::: Type -> b ::: Type -> (a -> option b) -> t a -> t b + +val get : a ::: Type -> a -> option a -> a +val unsafeGet : a ::: Type -> option a -> a diff --git a/lib/ur/string.ur b/lib/ur/string.ur new file mode 100644 index 0000000..da4e7eb --- /dev/null +++ b/lib/ur/string.ur @@ -0,0 +1,113 @@ +type t = Basis.string + +val str = Basis.str1 + +val length = Basis.strlen +val lengthGe = Basis.strlenGe +val append = Basis.strcat + +val sub = Basis.strsub +val suffix = Basis.strsuffix + +val index = Basis.strindex +fun sindex r = Basis.strsindex r.Haystack r.Needle +val atFirst = Basis.strchr + +fun mindex {Haystack = s, Needle = chs} = + let + val n = Basis.strcspn s chs + in + if n >= length s then + None + else + Some n + end + +fun substring s {Start = start, Len = len} = Basis.substring s start len + +fun seek s ch = + case index s ch of + None => None + | Some i => Some (suffix s (i + 1)) +fun mseek {Haystack = s, Needle = chs} = + case mindex {Haystack = s, Needle = chs} of + None => None + | Some i => Some (sub s i, suffix s (i + 1)) + +fun split s ch = + case index s ch of + None => None + | Some i => Some (substring s {Start = 0, Len = i}, + suffix s (i + 1)) +fun split' s ch = + case index s ch of + None => None + | Some i => Some (substring s {Start = 0, Len = i}, + suffix s i) +fun msplit {Haystack = s, Needle = chs} = + case mindex {Haystack = s, Needle = chs} of + None => None + | Some i => Some (substring s {Start = 0, Len = i}, + sub s i, + suffix s (i + 1)) + +fun ssplit r = + case sindex r of + None => None + | Some i => Some (substring r.Haystack {Start = 0, Len = i}, + suffix r.Haystack (i + length r.Needle)) + +fun all f s = + let + val len = length s + + fun al i = + i >= len + || (f (sub s i) && al (i + 1)) + in + al 0 + end + +fun mp f s = + let + fun mp' i acc = + if i < 0 then + acc + else + mp' (i - 1) (str (f (sub s i)) ^ acc) + in + mp' (length s - 1) "" + end + +fun newlines [ctx] [[Body] ~ ctx] (s : string) : xml ([Body] ++ ctx) [] [] = + case split s #"\n" of + None => cdata s + | Some (s1, s2) => <xml>{[s1]}<br/>{newlines s2}</xml> + +fun isPrefix {Full = f, Prefix = p} = + length f >= length p && substring f {Start = 0, Len = length p} = p + +fun trim s = + let + val len = length s + + fun findStart i = + if i < len && isspace (sub s i) then + findStart (i+1) + else + i + + fun findFinish i = + if i >= 0 && isspace (sub s i) then + findFinish (i-1) + else + i + + val start = findStart 0 + val finish = findFinish (len - 1) + in + if finish >= start then + substring s {Start = start, Len = finish - start + 1} + else + "" + end diff --git a/lib/ur/string.urs b/lib/ur/string.urs new file mode 100644 index 0000000..1bdca96 --- /dev/null +++ b/lib/ur/string.urs @@ -0,0 +1,37 @@ +type t = string + +val str : char -> t + +val length : t -> int +val lengthGe : t -> int -> bool + +val append : t -> t -> t + +val sub : t -> int -> char +val suffix : t -> int -> string + +val index : t -> char -> option int +val sindex : {Haystack : t, Needle : t} -> option int +val atFirst : t -> char -> option string + +val mindex : {Haystack : t, Needle : t} -> option int + +val substring : t -> {Start : int, Len : int} -> string + +val seek : t -> char -> option string +val mseek : {Haystack : t, Needle : t} -> option (char * string) + +val split : t -> char -> option (string * string) +val split' : t -> char -> option (string * string) (* The matched character is kept at the beginning of the suffix. *) +val msplit : {Haystack : t, Needle : t} -> option (string * char * string) + +val ssplit : {Haystack : t, Needle : t} -> option (string * string) + +val all : (char -> bool) -> string -> bool +val mp : (char -> char) -> string -> string + +val newlines : ctx ::: {Unit} -> [[Body] ~ ctx] => string -> xml ([Body] ++ ctx) [] [] + +val isPrefix : {Full : t, Prefix : t} -> bool + +val trim : t -> t diff --git a/lib/ur/top.ur b/lib/ur/top.ur new file mode 100644 index 0000000..0256791 --- /dev/null +++ b/lib/ur/top.ur @@ -0,0 +1,430 @@ +(** Row folding *) + +con folder = K ==> fn r :: {K} => + tf :: ({K} -> Type) + -> (nm :: Name -> v :: K -> r :: {K} -> [[nm] ~ r] => + tf r -> tf ([nm = v] ++ r)) + -> tf [] -> tf r + +fun fold [K] [tf :: {K} -> Type] + (f : (nm :: Name -> v :: K -> r :: {K} -> [[nm] ~ r] => + tf r -> tf ([nm = v] ++ r))) + (i : tf []) [r ::: {K}] (fl : folder r) = fl [tf] f i + +structure Folder = struct + fun nil [K] [tf :: {K} -> Type] + (f : nm :: Name -> v :: K -> r :: {K} -> [[nm] ~ r] => + tf r -> tf ([nm = v] ++ r)) + (i : tf []) = i + + fun cons [K] [r ::: {K}] [nm :: Name] [v :: K] [[nm] ~ r] (fold : folder r) + [tf :: {K} -> Type] + (f : nm :: Name -> v :: K -> r :: {K} -> [[nm] ~ r] => + tf r -> tf ([nm = v] ++ r)) + (i : tf []) = f [nm] [v] [r] (fold [tf] f i) + + fun concat [K] [r1 ::: {K}] [r2 ::: {K}] [r1 ~ r2] + (f1 : folder r1) (f2 : folder r2) + [tf :: {K} -> Type] + (f : nm :: Name -> v :: K -> r :: {K} -> [[nm] ~ r] => + tf r -> tf ([nm = v] ++ r)) + (i : tf []) = + f1 [fn r1' => [r1' ~ r2] => tf (r1' ++ r2)] + (fn [nm :: Name] [v :: K] [r1' :: {K}] [[nm] ~ r1'] + (acc : [r1' ~ r2] => tf (r1' ++ r2)) + [[nm = v] ++ r1' ~ r2] => + f [nm] [v] [r1' ++ r2] acc) + (fn [[] ~ r2] => f2 [tf] f i) + + fun mp [K1] [K2] [f ::: K1 -> K2] [r ::: {K1}] + (fold : folder r) + [tf :: {K2} -> Type] + (f : nm :: Name -> v :: K2 -> r :: {K2} -> [[nm] ~ r] => + tf r -> tf ([nm = v] ++ r)) + (i : tf []) = + fold [fn r => tf (map f r)] + (fn [nm :: Name] [v :: K1] [rest :: {K1}] [[nm] ~ rest] (acc : tf (map f rest)) => + f [nm] [f v] [map f rest] acc) + i +end + + +fun not b = if b then False else True + +con ident = K ==> fn t :: K => t +con record (t :: {Type}) = $t +con fst = K1 ==> K2 ==> fn t :: (K1 * K2) => t.1 +con snd = K1 ==> K2 ==> fn t :: (K1 * K2) => t.2 +con fst3 = K1 ==> K2 ==> K3 ==> fn t :: (K1 * K2 * K3) => t.1 +con snd3 = K1 ==> K2 ==> K3 ==> fn t :: (K1 * K2 * K3) => t.2 +con thd3 = K1 ==> K2 ==> K3 ==> fn t :: (K1 * K2 * K3) => t.3 + +con mapU = K ==> fn f :: K => map (fn _ :: Unit => f) + +con ex = K ==> fn tf :: (K -> Type) => + res ::: Type -> (choice :: K -> tf choice -> res) -> res + +fun ex_intro [K] [tf :: K -> Type] [choice :: K] (body : tf choice) : ex tf = + fn [res] (f : choice :: K -> tf choice -> res) => + f [choice] body + +fun ex_elim [K] [tf ::: K -> Type] (v : ex tf) [res ::: Type] = @@v [res] + +fun compose [t1 ::: Type] [t2 ::: Type] [t3 ::: Type] + (f1 : t2 -> t3) (f2 : t1 -> t2) (x : t1) = f1 (f2 x) + +fun show_option [t ::: Type] (_ : show t) = + mkShow (fn opt : option t => + case opt of + None => "" + | Some x => show x) + +fun read_option [t ::: Type] (_ : read t) = + mkRead (fn s => + case s of + "" => None + | _ => Some (readError s : t)) + (fn s => + case s of + "" => Some None + | _ => case read s of + None => None + | v => Some v) + +fun txt [t] [ctx ::: {Unit}] [use ::: {Type}] (_ : show t) (v : t) : xml ctx use [] = + cdata (show v) + +fun map0 [K] [tf :: K -> Type] (f : t :: K -> tf t) [r ::: {K}] (fl : folder r) = + fl [fn r :: {K} => $(map tf r)] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] acc => + acc ++ {nm = f [t]}) + {} + +fun mp [K] [tf1 :: K -> Type] [tf2 :: K -> Type] (f : t ::: K -> tf1 t -> tf2 t) [r ::: {K}] (fl : folder r) = + fl [fn r :: {K} => $(map tf1 r) -> $(map tf2 r)] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] acc r => + acc (r -- nm) ++ {nm = f r.nm}) + (fn _ => {}) + +fun map2 [K] [tf1 :: K -> Type] [tf2 :: K -> Type] [tf3 :: K -> Type] + (f : t ::: K -> tf1 t -> tf2 t -> tf3 t) [r ::: {K}] (fl : folder r) = + fl [fn r :: {K} => $(map tf1 r) -> $(map tf2 r) -> $(map tf3 r)] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] acc r1 r2 => + acc (r1 -- nm) (r2 -- nm) ++ {nm = f r1.nm r2.nm}) + (fn _ _ => {}) + +fun map3 [K] [tf1 :: K -> Type] [tf2 :: K -> Type] [tf3 :: K -> Type] [tf :: K -> Type] + (f : t ::: K -> tf1 t -> tf2 t -> tf3 t -> tf t) [r ::: {K}] (fl : folder r) = + fl [fn r :: {K} => $(map tf1 r) -> $(map tf2 r) -> $(map tf3 r) -> $(map tf r)] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] acc r1 r2 r3 => + acc (r1 -- nm) (r2 -- nm) (r3 -- nm) ++ {nm = f r1.nm r2.nm r3.nm}) + (fn _ _ _ => {}) + +fun foldUR [tf :: Type] [tr :: {Unit} -> Type] + (f : nm :: Name -> rest :: {Unit} + -> [[nm] ~ rest] => + tf -> tr rest -> tr ([nm] ++ rest)) + (i : tr []) [r ::: {Unit}] (fl : folder r) = + fl [fn r :: {Unit} => $(mapU tf r) -> tr r] + (fn [nm :: Name] [t :: Unit] [rest :: {Unit}] [[nm] ~ rest] acc r => + f [nm] [rest] r.nm (acc (r -- nm))) + (fn _ => i) + +fun foldUR2 [tf1 :: Type] [tf2 :: Type] [tr :: {Unit} -> Type] + (f : nm :: Name -> rest :: {Unit} + -> [[nm] ~ rest] => + tf1 -> tf2 -> tr rest -> tr ([nm] ++ rest)) + (i : tr []) [r ::: {Unit}] (fl : folder r) = + fl [fn r :: {Unit} => $(mapU tf1 r) -> $(mapU tf2 r) -> tr r] + (fn [nm :: Name] [t :: Unit] [rest :: {Unit}] [[nm] ~ rest] acc r1 r2 => + f [nm] [rest] r1.nm r2.nm (acc (r1 -- nm) (r2 -- nm))) + (fn _ _ => i) + +fun foldR [K] [tf :: K -> Type] [tr :: {K} -> Type] + (f : nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf t -> tr rest -> tr ([nm = t] ++ rest)) + (i : tr []) [r ::: {K}] (fl : folder r) = + fl [fn r :: {K} => $(map tf r) -> tr r] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] (acc : _ -> tr rest) r => + f [nm] [t] [rest] r.nm (acc (r -- nm))) + (fn _ => i) + +fun foldR2 [K] [tf1 :: K -> Type] [tf2 :: K -> Type] [tr :: {K} -> Type] + (f : nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> tr rest -> tr ([nm = t] ++ rest)) + (i : tr []) [r ::: {K}] (fl : folder r) = + fl [fn r :: {K} => $(map tf1 r) -> $(map tf2 r) -> tr r] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] + (acc : _ -> _ -> tr rest) r1 r2 => + f [nm] [t] [rest] r1.nm r2.nm (acc (r1 -- nm) (r2 -- nm))) + (fn _ _ => i) + +fun foldR3 [K] [tf1 :: K -> Type] [tf2 :: K -> Type] [tf3 :: K -> Type] [tr :: {K} -> Type] + (f : nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> tf3 t -> tr rest -> tr ([nm = t] ++ rest)) + (i : tr []) [r ::: {K}] (fl : folder r) = + fl [fn r :: {K} => $(map tf1 r) -> $(map tf2 r) -> $(map tf3 r) -> tr r] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] + (acc : _ -> _ -> _ -> tr rest) r1 r2 r3 => + f [nm] [t] [rest] r1.nm r2.nm r3.nm (acc (r1 -- nm) (r2 -- nm) (r3 -- nm))) + (fn _ _ _ => i) + +fun mapUX [tf :: Type] [ctx :: {Unit}] + (f : nm :: Name -> rest :: {Unit} -> [[nm] ~ rest] => tf -> xml ctx [] []) = + @@foldR [fn _ => tf] [fn _ => xml ctx [] []] + (fn [nm :: Name] [u :: Unit] [rest :: {Unit}] [[nm] ~ rest] r acc => + <xml>{f [nm] [rest] r}{acc}</xml>) + <xml/> + +fun mapUX_rev [tf :: Type] [ctx :: {Unit}] + (f : nm :: Name -> rest :: {Unit} -> [[nm] ~ rest] => tf -> xml ctx [] []) = + @@foldR [fn _ => tf] [fn _ => xml ctx [] []] + (fn [nm :: Name] [u :: Unit] [rest :: {Unit}] [[nm] ~ rest] r acc => + <xml>{acc}{f [nm] [rest] r}</xml>) + <xml/> + +fun mapX [K] [tf :: K -> Type] [ctx :: {Unit}] + (f : nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf t -> xml ctx [] []) = + @@foldR [tf] [fn _ => xml ctx [] []] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] r acc => + <xml>{f [nm] [t] [rest] r}{acc}</xml>) + <xml/> + +fun mapUX2 [tf1 :: Type] [tf2 :: Type] [ctx :: {Unit}] + (f : nm :: Name -> rest :: {Unit} + -> [[nm] ~ rest] => + tf1 -> tf2 -> xml ctx [] []) = + @@foldUR2 [tf1] [tf2] [fn _ => xml ctx [] []] + (fn [nm :: Name] [rest :: {Unit}] [[nm] ~ rest] v1 v2 acc => + <xml>{f [nm] [rest] v1 v2}{acc}</xml>) + <xml/> + +fun mapX2 [K] [tf1 :: K -> Type] [tf2 :: K -> Type] [ctx :: {Unit}] + (f : nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> xml ctx [] []) = + @@foldR2 [tf1] [tf2] [fn _ => xml ctx [] []] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] + r1 r2 acc => + <xml>{f [nm] [t] [rest] r1 r2}{acc}</xml>) + <xml/> + +fun mapX3 [K] [tf1 :: K -> Type] [tf2 :: K -> Type] [tf3 :: K -> Type] [ctx :: {Unit}] + (f : nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> tf3 t -> xml ctx [] []) = + @@foldR3 [tf1] [tf2] [tf3] [fn _ => xml ctx [] []] + (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] + r1 r2 r3 acc => + <xml>{f [nm] [t] [rest] r1 r2 r3}{acc}</xml>) + <xml/> + +fun query1 [t ::: Name] [fs ::: {Type}] [state ::: Type] (q : sql_query [] [] [t = fs] []) + (f : $fs -> state -> transaction state) (i : state) = + query q (fn r => f r.t) i + +fun query1' [t ::: Name] [fs ::: {Type}] [state ::: Type] (q : sql_query [] [] [t = fs] []) + (f : $fs -> state -> state) (i : state) = + query q (fn r s => return (f r.t s)) i + +val rev = fn [a] => + let + fun rev' acc (ls : list a) = + case ls of + [] => acc + | x :: ls => rev' (x :: acc) ls + in + rev' [] + end + +fun queryL [tables] [exps] [tables ~ exps] (q : sql_query [] [] tables exps) = + ls <- query q (fn r ls => return (r :: ls)) []; + return (rev ls) + +fun queryL1 [t ::: Name] [fs ::: {Type}] (q : sql_query [] [] [t = fs] []) = + ls <- query q (fn r ls => return (r.t :: ls)) []; + return (rev ls) + +fun queryI [tables ::: {{Type}}] [exps ::: {Type}] + [tables ~ exps] (q : sql_query [] [] tables exps) + (f : $(exps ++ map (fn fields :: {Type} => $fields) tables) + -> transaction unit) = + query q + (fn fs _ => f fs) + () + +fun queryI1 [nm ::: Name] [fs ::: {Type}] (q : sql_query [] [] [nm = fs] []) + (f : $fs -> transaction unit) = + query q + (fn fs _ => f fs.nm) + () + +fun queryX [tables ::: {{Type}}] [exps ::: {Type}] [ctx ::: {Unit}] [inp ::: {Type}] + [tables ~ exps] (q : sql_query [] [] tables exps) + (f : $(exps ++ map (fn fields :: {Type} => $fields) tables) + -> xml ctx inp []) = + query q + (fn fs acc => return <xml>{acc}{f fs}</xml>) + <xml/> + +fun rev [a] (ls : list a) : list a = + let + fun rev' ls acc = + case ls of + [] => acc + | x :: ls => rev' ls (x :: acc) + in + rev' ls [] + end + +fun queryXI [tables ::: {{Type}}] [exps ::: {Type}] [ctx ::: {Unit}] [inp ::: {Type}] + [tables ~ exps] (q : sql_query [] [] tables exps) + (f : int -> $(exps ++ map (fn fields :: {Type} => $fields) tables) + -> xml ctx inp []) = + let + fun qxi ls i = + case ls of + [] => <xml/> + | x :: ls => <xml>{f i x}{qxi ls (i+1)}</xml> + in + ls <- queryL q; + return (qxi (rev ls) 0) + end + +fun queryX1 [nm ::: Name] [fs ::: {Type}] [ctx ::: {Unit}] [inp ::: {Type}] + (q : sql_query [] [] [nm = fs] []) + (f : $fs -> xml ctx inp []) = + query q + (fn fs acc => return <xml>{acc}{f fs.nm}</xml>) + <xml/> + +fun queryX1I [nm ::: Name] [fs ::: {Type}] [ctx ::: {Unit}] [inp ::: {Type}] + (q : sql_query [] [] [nm = fs] []) + (f : int -> $fs -> xml ctx inp []) = + let + fun qx1i ls i = + case ls of + [] => <xml/> + | x :: ls => <xml>{f i x.nm}{qx1i ls (i+1)}</xml> + in + ls <- queryL q; + return (qx1i (rev ls) 0) + end + +fun queryX' [tables ::: {{Type}}] [exps ::: {Type}] [ctx ::: {Unit}] [inp ::: {Type}] + [tables ~ exps] (q : sql_query [] [] tables exps) + (f : $(exps ++ map (fn fields :: {Type} => $fields) tables) + -> transaction (xml ctx inp [])) = + query q + (fn fs acc => + r <- f fs; + return <xml>{acc}{r}</xml>) + <xml/> + +fun queryX1' [nm ::: Name] [fs ::: {Type}] [ctx ::: {Unit}] [inp ::: {Type}] + (q : sql_query [] [] [nm = fs] []) + (f : $fs -> transaction (xml ctx inp [])) = + query q + (fn fs acc => + r <- f fs.nm; + return <xml>{acc}{r}</xml>) + <xml/> + +fun queryXE' [exps ::: {Type}] [ctx ::: {Unit}] [inp ::: {Type}] + (q : sql_query [] [] [] exps) + (f : $exps -> transaction (xml ctx inp [])) = + query q + (fn fs acc => + r <- f fs; + return <xml>{acc}{r}</xml>) + <xml/> + +fun hasRows [tables ::: {{Type}}] [exps ::: {Type}] + [tables ~ exps] + (q : sql_query [] [] tables exps) = + query q + (fn _ _ => return True) + False + +fun oneOrNoRows [tables ::: {{Type}}] [exps ::: {Type}] + [tables ~ exps] + (q : sql_query [] [] tables exps) = + query q + (fn fs _ => return (Some fs)) + None + +fun oneOrNoRows1 [nm ::: Name] [fs ::: {Type}] (q : sql_query [] [] [nm = fs] []) = + query q + (fn fs _ => return (Some fs.nm)) + None + +fun oneOrNoRowsE1 [tabs ::: {Unit}] [nm ::: Name] [t ::: Type] [tabs ~ [nm]] (q : sql_query [] [] (mapU [] tabs) [nm = t]) = + query q + (fn fs _ => return (Some fs.nm)) + None + +fun oneRow [tables ::: {{Type}}] [exps ::: {Type}] + [tables ~ exps] (q : sql_query [] [] tables exps) = + o <- oneOrNoRows q; + return (case o of + None => error <xml>Query returned no rows</xml> + | Some r => r) + +fun oneRow1 [nm ::: Name] [fs ::: {Type}] (q : sql_query [] [] [nm = fs] []) = + o <- oneOrNoRows q; + return (case o of + None => error <xml>Query returned no rows</xml> + | Some r => r.nm) + +fun oneRowE1 [tabs ::: {Unit}] [nm ::: Name] [t ::: Type] [tabs ~ [nm]] (q : sql_query [] [] (mapU [] tabs) [nm = t]) = + o <- oneOrNoRows q; + return (case o of + None => error <xml>Query returned no rows</xml> + | Some r => r.nm) + +fun nonempty [fs] [us] (t : sql_table fs us) = + oneRowE1 (SELECT COUNT( * ) > 0 AS B FROM t) + +fun eqNullable [tables ::: {{Type}}] [agg ::: {{Type}}] [exps ::: {Type}] + [t ::: Type] (_ : sql_injectable (option t)) + (e1 : sql_exp tables agg exps (option t)) + (e2 : sql_exp tables agg exps (option t)) = + (SQL ({e1} IS NULL AND {e2} IS NULL) OR {e1} = {e2}) + +fun eqNullable' [tables ::: {{Type}}] [agg ::: {{Type}}] [exps ::: {Type}] + [t ::: Type] (_ : sql_injectable (option t)) + (e1 : sql_exp tables agg exps (option t)) + (e2 : option t) = + case e2 of + None => (SQL {e1} IS NULL) + | Some _ => sql_binary sql_eq e1 (sql_inject e2) + +fun mkRead' [t ::: Type] (f : string -> option t) (name : string) : read t = + mkRead (fn s => case f s of + None => error <xml>Invalid {txt name}: {txt s}</xml> + | Some v => v) f + +fun postFields pb = + let + fun postFields' s = + case firstFormField s of + None => [] + | Some f => (fieldName f, fieldValue f) :: postFields' (remainingFields f) + in + case postType pb of + "application/x-www-form-urlencoded" => postFields' (postData pb) + | _ => error <xml>Tried to get POST fields, but MIME type is not "application/x-www-form-urlencoded"</xml> + end + +fun max [t] ( _ : ord t) (x : t) (y : t) : t = + if x > y then x else y +fun min [t] ( _ : ord t) (x : t) (y : t) : t = + if x < y then x else y + +fun assert [a] (cond: bool) (msg: string) (loc: string) (x:a): a = + if cond then x else error <xml>{txt msg} at {txt loc}</xml> diff --git a/lib/ur/top.urs b/lib/ur/top.urs new file mode 100644 index 0000000..ec09895 --- /dev/null +++ b/lib/ur/top.urs @@ -0,0 +1,303 @@ +(** Row folding *) + +con folder :: K --> {K} -> Type + +val fold : K --> tf :: ({K} -> Type) + -> (nm :: Name -> v :: K -> r :: {K} -> [[nm] ~ r] => + tf r -> tf ([nm = v] ++ r)) + -> tf [] + -> r ::: {K} -> folder r -> tf r + +structure Folder : sig + val nil : K --> folder (([]) :: {K}) + val cons : K --> r ::: {K} -> nm :: Name -> v :: K + -> [[nm] ~ r] => folder r -> folder ([nm = v] ++ r) + val concat : K --> r1 ::: {K} -> r2 ::: {K} + -> [r1 ~ r2] => folder r1 -> folder r2 -> folder (r1 ++ r2) + val mp : K1 --> K2 --> f ::: (K1 -> K2) -> r ::: {K1} + -> folder r -> folder (map f r) +end + + +val not : bool -> bool + +(* Type-level identity function *) +con ident = K ==> fn t :: K => t + +(* Type-level function which yields the value-level record + described by the given type-level record *) +con record = fn t :: {Type} => $t + +con fst = K1 ==> K2 ==> fn t :: (K1 * K2) => t.1 +con snd = K1 ==> K2 ==> fn t :: (K1 * K2) => t.2 +con fst3 = K1 ==> K2 ==> K3 ==> fn t :: (K1 * K2 * K3) => t.1 +con snd3 = K1 ==> K2 ==> K3 ==> fn t :: (K1 * K2 * K3) => t.2 +con thd3 = K1 ==> K2 ==> K3 ==> fn t :: (K1 * K2 * K3) => t.3 + +(* Convert a record of n Units into a type-level record where + each field has the same value (which describes a uniformly + typed record) *) +con mapU = K ==> fn f :: K => map (fn _ :: Unit => f) + +(* Existential type former *) +con ex :: K --> (K -> Type) -> Type + +(* Introduction of existential type *) +val ex_intro : K --> tf :: (K -> Type) -> choice :: K -> tf choice -> ex tf + +(* Eliminator for existential type *) +val ex_elim : K --> tf ::: (K -> Type) -> ex tf -> res ::: Type -> (choice :: K -> tf choice -> res) -> res + +(* Composition of ordinary (value-level) functions *) +val compose : t1 ::: Type -> t2 ::: Type -> t3 ::: Type + -> (t2 -> t3) -> (t1 -> t2) -> (t1 -> t3) + +val show_option : t ::: Type -> show t -> show (option t) +val read_option : t ::: Type -> read t -> read (option t) + +val txt : t ::: Type -> ctx ::: {Unit} -> use ::: {Type} -> show t -> t + -> xml ctx use [] + +(* Given a polymorphic type (tf) and a means of constructing + "default" values of tf applied to arbitrary arguments, + constructs records consisting of those "default" values *) +val map0 : K --> tf :: (K -> Type) + -> (t :: K -> tf t) + -> r ::: {K} -> folder r -> $(map tf r) + +(* Given two polymorphic types (tf1 and tf2) and a means of + converting from tf1 t to tf2 t for arbitrary t, + converts records of tf1's to records of tf2's *) +val mp : K --> tf1 :: (K -> Type) -> tf2 :: (K -> Type) + -> (t ::: K -> tf1 t -> tf2 t) + -> r ::: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r) + +(* Two-argument conversion form of mp *) +val map2 : K --> tf1 :: (K -> Type) -> tf2 :: (K -> Type) -> tf :: (K -> Type) + -> (t ::: K -> tf1 t -> tf2 t -> tf t) + -> r ::: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r) -> $(map tf r) + +(* Three-argument conversion form of mp *) +val map3 : K --> tf1 :: (K -> Type) -> tf2 :: (K -> Type) -> tf3 :: (K -> Type) -> tf :: (K -> Type) + -> (t ::: K -> tf1 t -> tf2 t -> tf3 t -> tf t) + -> r ::: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r) -> $(map tf3 r) -> $(map tf r) + +(* Fold along a uniformly (homogenously) typed record *) +val foldUR : tf :: Type -> tr :: ({Unit} -> Type) + -> (nm :: Name -> rest :: {Unit} + -> [[nm] ~ rest] => + tf -> tr rest -> tr ([nm] ++ rest)) + -> tr [] -> r ::: {Unit} -> folder r -> $(mapU tf r) -> tr r + +(* Fold (generalized safe zip) along two equal-length uniformly-typed records *) +val foldUR2 : tf1 :: Type -> tf2 :: Type -> tr :: ({Unit} -> Type) + -> (nm :: Name -> rest :: {Unit} + -> [[nm] ~ rest] => + tf1 -> tf2 -> tr rest -> tr ([nm] ++ rest)) + -> tr [] -> r ::: {Unit} -> folder r -> $(mapU tf1 r) -> $(mapU tf2 r) -> tr r + +(* Fold along a heterogenously-typed record *) +val foldR : K --> tf :: (K -> Type) -> tr :: ({K} -> Type) + -> (nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf t -> tr rest -> tr ([nm = t] ++ rest)) + -> tr [] -> r ::: {K} -> folder r -> $(map tf r) -> tr r + +(* Fold (generalized safe zip) along two heterogenously-typed records *) +val foldR2 : K --> tf1 :: (K -> Type) -> tf2 :: (K -> Type) + -> tr :: ({K} -> Type) + -> (nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> tr rest -> tr ([nm = t] ++ rest)) + -> tr [] + -> r ::: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r) -> tr r + +(* Fold (generalized safe zip) along three heterogenously-typed records *) +val foldR3 : K --> tf1 :: (K -> Type) -> tf2 :: (K -> Type) -> tf3 :: (K -> Type) + -> tr :: ({K} -> Type) + -> (nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> tf3 t -> tr rest -> tr ([nm = t] ++ rest)) + -> tr [] + -> r ::: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r) -> $(map tf3 r) -> tr r + +(* Generate some XML by mapping over a uniformly-typed record *) +val mapUX : tf :: Type -> ctx :: {Unit} + -> (nm :: Name -> rest :: {Unit} -> [[nm] ~ rest] => + tf -> xml ctx [] []) + -> r ::: {Unit} -> folder r -> $(mapU tf r) -> xml ctx [] [] +val mapUX_rev : tf :: Type -> ctx :: {Unit} + -> (nm :: Name -> rest :: {Unit} -> [[nm] ~ rest] => + tf -> xml ctx [] []) + -> r ::: {Unit} -> folder r -> $(mapU tf r) -> xml ctx [] [] + +(* Generate some XML by mapping over a heterogenously-typed record *) +val mapX : K --> tf :: (K -> Type) -> ctx :: {Unit} + -> (nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf t -> xml ctx [] []) + -> r ::: {K} -> folder r -> $(map tf r) -> xml ctx [] [] + +val mapUX2 : tf1 :: Type -> tf2 :: Type -> ctx :: {Unit} + -> (nm :: Name -> rest :: {Unit} + -> [[nm] ~ rest] => + tf1 -> tf2 -> xml ctx [] []) + -> r ::: {Unit} -> folder r + -> $(mapU tf1 r) -> $(mapU tf2 r) -> xml ctx [] [] + +val mapX2 : K --> tf1 :: (K -> Type) -> tf2 :: (K -> Type) -> ctx :: {Unit} + -> (nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> xml ctx [] []) + -> r ::: {K} -> folder r + -> $(map tf1 r) -> $(map tf2 r) -> xml ctx [] [] + +val mapX3 : K --> tf1 :: (K -> Type) -> tf2 :: (K -> Type) -> tf3 :: (K -> Type) -> ctx :: {Unit} + -> (nm :: Name -> t :: K -> rest :: {K} + -> [[nm] ~ rest] => + tf1 t -> tf2 t -> tf3 t -> xml ctx [] []) + -> r ::: {K} -> folder r + -> $(map tf1 r) -> $(map tf2 r) -> $(map tf3 r) -> xml ctx [] [] + +(* Note that the next two functions return elements in the _reverse_ of the natural order! + * Such a choice interacts well with the time complexity of standard list operations. + * It's easy to regain the natural order by inverting a query's 'ORDER BY' condition. *) + +val queryL : tables ::: {{Type}} -> exps ::: {Type} + -> [tables ~ exps] => + sql_query [] [] tables exps + -> transaction (list $(exps ++ map (fn fields :: {Type} => $fields) tables)) + +val queryL1 : t ::: Name -> fs ::: {Type} + -> sql_query [] [] [t = fs] [] + -> transaction (list $fs) + +val query1 : t ::: Name -> fs ::: {Type} -> state ::: Type + -> sql_query [] [] [t = fs] [] + -> ($fs -> state -> transaction state) + -> state + -> transaction state + +val query1' : t ::: Name -> fs ::: {Type} -> state ::: Type + -> sql_query [] [] [t = fs] [] + -> ($fs -> state -> state) + -> state + -> transaction state + +val queryI : tables ::: {{Type}} -> exps ::: {Type} + -> [tables ~ exps] => + sql_query [] [] tables exps + -> ($(exps ++ map (fn fields :: {Type} => $fields) tables) + -> transaction unit) + -> transaction unit + +val queryI1 : nm ::: Name -> fs ::: {Type} + -> sql_query [] [] [nm = fs] [] + -> ($fs -> transaction unit) + -> transaction unit + +val queryX : tables ::: {{Type}} -> exps ::: {Type} -> ctx ::: {Unit} -> inp ::: {Type} + -> [tables ~ exps] => + sql_query [] [] tables exps + -> ($(exps ++ map (fn fields :: {Type} => $fields) tables) + -> xml ctx inp []) + -> transaction (xml ctx inp []) + +val queryXI : tables ::: {{Type}} -> exps ::: {Type} -> ctx ::: {Unit} -> inp ::: {Type} + -> [tables ~ exps] => + sql_query [] [] tables exps + -> (int -> $(exps ++ map (fn fields :: {Type} => $fields) tables) + -> xml ctx inp []) + -> transaction (xml ctx inp []) + +val queryX1 : nm ::: Name -> fs ::: {Type} -> ctx ::: {Unit} -> inp ::: {Type} + -> sql_query [] [] [nm = fs] [] + -> ($fs -> xml ctx inp []) + -> transaction (xml ctx inp []) + +val queryX1I : nm ::: Name -> fs ::: {Type} -> ctx ::: {Unit} -> inp ::: {Type} + -> sql_query [] [] [nm = fs] [] + -> (int -> $fs -> xml ctx inp []) + -> transaction (xml ctx inp []) + +val queryX' : tables ::: {{Type}} -> exps ::: {Type} -> ctx ::: {Unit} -> inp ::: {Type} + -> [tables ~ exps] => + sql_query [] [] tables exps + -> ($(exps ++ map (fn fields :: {Type} => $fields) tables) + -> transaction (xml ctx inp [])) + -> transaction (xml ctx inp []) +val queryX1' : nm ::: Name -> fs ::: {Type} -> ctx ::: {Unit} -> inp ::: {Type} + -> sql_query [] [] [nm = fs] [] + -> ($fs -> transaction (xml ctx inp [])) + -> transaction (xml ctx inp []) +val queryXE' : exps ::: {Type} -> ctx ::: {Unit} -> inp ::: {Type} + -> sql_query [] [] [] exps + -> ($exps -> transaction (xml ctx inp [])) + -> transaction (xml ctx inp []) + +val hasRows : tables ::: {{Type}} -> exps ::: {Type} + -> [tables ~ exps] => + sql_query [] [] tables exps + -> transaction bool + +val oneOrNoRows : tables ::: {{Type}} -> exps ::: {Type} + -> [tables ~ exps] => + sql_query [] [] tables exps + -> transaction + (option + $(exps + ++ map (fn fields :: {Type} => $fields) tables)) + +val oneOrNoRows1 : nm ::: Name -> fs ::: {Type} + -> sql_query [] [] [nm = fs] [] + -> transaction (option $fs) + +val oneOrNoRowsE1 : tabs ::: {Unit} -> nm ::: Name -> t ::: Type + -> [tabs ~ [nm]] => + sql_query [] [] (mapU [] tabs) [nm = t] + -> transaction (option t) + +val oneRow : tables ::: {{Type}} -> exps ::: {Type} + -> [tables ~ exps] => + sql_query [] [] tables exps + -> transaction + $(exps + ++ map (fn fields :: {Type} => $fields) tables) + +val oneRow1 : nm ::: Name -> fs ::: {Type} + -> sql_query [] [] [nm = fs] [] + -> transaction $fs + +val oneRowE1 : tabs ::: {Unit} -> nm ::: Name -> t ::: Type + -> [tabs ~ [nm]] => + sql_query [] [] (mapU [] tabs) [nm = t] + -> transaction t + +val nonempty : fs ::: {Type} -> us ::: {{Unit}} -> sql_table fs us + -> transaction bool + +val eqNullable : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> t ::: Type -> sql_injectable (option t) + -> sql_exp tables agg exps (option t) + -> sql_exp tables agg exps (option t) + -> sql_exp tables agg exps bool + +val eqNullable' : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} + -> t ::: Type -> sql_injectable (option t) + -> sql_exp tables agg exps (option t) + -> option t + -> sql_exp tables agg exps bool + +val mkRead' : t ::: Type -> (string -> option t) -> string -> read t + +val postFields : postBody -> list (string * string) + +val max : t ::: Type -> ord t -> t -> t -> t +val min : t ::: Type -> ord t -> t -> t -> t + +val assert : t ::: Type + -> bool (* Did we avoid something bad? *) + -> string (* Explanation of the bad thing *) + -> string (* Source location of the bad thing *) + -> t (* Return this value if all went well. *) + -> t |