From 61bd40e1af8c3f7ace2a09068557ac7c05662b69 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 27 Nov 2008 14:38:53 -0500 Subject: Start of manual --- doc/Makefile | 23 ++++++++++++++++ doc/manual.tex | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 doc/Makefile create mode 100644 doc/manual.tex (limited to 'doc') diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 00000000..777c5bf7 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,23 @@ +PAPERS=manual + +FIGURES= + +all: $(PAPERS:%=%.dvi) $(PAPERS:%=%.ps) $(PAPERS:%=%.pdf) + +%.dvi: %.tex $(FIGURES:%=%.eps) + latex $< + latex $< + +%.ps: %.dvi + dvips $< -o $@ + +%.pdf: %.dvi $(FIGURES:%=%.pdf) + pdflatex $(<:%.dvi=%) + +%.pdf: %.eps + epstopdf $< + +clean: + rm -f *.aux *.bbl *.blg *.dvi *.log *.pdf *.ps + +.PHONY: all clean diff --git a/doc/manual.tex b/doc/manual.tex new file mode 100644 index 00000000..8517206a --- /dev/null +++ b/doc/manual.tex @@ -0,0 +1,85 @@ +\documentclass{article} +\usepackage{fullpage,amsmath,amssymb,proof} + +\newcommand{\cd}[1]{\texttt{#1}} +\newcommand{\mt}[1]{\mathsf{#1}} + +\newcommand{\rc}{+ \hspace{-.075in} + \;} + +\begin{document} + +\title{The Ur/Web Manual} +\author{Adam Chlipala} + +\maketitle + +\section{Syntax} + +\subsection{Lexical Conventions} + +We give the Ur language definition in \LaTeX $\;$ math mode, since that is prettier than monospaced ASCII. The corresponding ASCII syntax can be read off directly. Here is the key for mapping math symbols to ASCII character sequences. + +\begin{center} + \begin{tabular}{rl} + \textbf{\LaTeX} & \textbf{ASCII} \\ + $\to$ & \cd{->} \\ + $\times$ & \cd{*} \\ + $\lambda$ & \cd{fn} \\ + $\Rightarrow$ & \cd{=>} \\ + $\rc$ & \cd{++} \\ + \\ + $x$ & Normal textual identifier, not beginning with an uppercase letter \\ + $\alpha$ & Normal textual identifier, not beginning with an uppercase letter \\ + $f$ & Normal textual identifier, beginning with an uppercase letter \\ + \end{tabular} +\end{center} + +We often write syntax like $N, \cdots, N$ to stand for the non-terminal $N$ repeated 0 or more times. That is, the $\cdots$ symbol is not translated literally to ASCII. + +\subsection{Core Syntax} + +\emph{Kinds} classify types and other compile-time-only entities. Each kind in the grammar is listed with a description of the sort of data it classifies. +$$\begin{array}{rrcll} + \textrm{Kinds} & \kappa &::=& \mt{Type} & \textrm{proper types} \\ + &&& \mid \mt{Unit} & \textrm{the trivial constructor} \\ + &&& \mid \mt{Name} & \textrm{field names} \\ + &&& \mid \kappa \to \kappa & \textrm{type-level functions} \\ + &&& \mid \{\kappa\} & \textrm{type-level records} \\ + &&& \mid (\kappa \times \cdots \times \kappa) & \textrm{type-level tuples} \\ + &&& \mid (\kappa) & \textrm{explicit precedence} \\ +\end{array}$$ + +Ur supports several different notions of functions that take types as arguments. These arguments can be either implicit, causing them to be inferred at use sites; or explicit, forcing them to be specified manually at use sites. There is a common explicitness annotation convention applied at the definitions of and in the types of such functions. +$$\begin{array}{rrcll} + \textrm{Explicitness} & ? &::=& :: & \textrm{explicit} \\ + &&& \mid \; ::: & \textrm{implicit} +\end{array}$$ + +\emph{Constructors} are the main class of compile-time-only data. They include proper types and are classified by kinds. +$$\begin{array}{rrcll} + \textrm{Constructors} & c, \tau &::=& (c) :: \kappa & \textrm{kind annotation} \\ + &&& \mid \alpha & \textrm{constructor variable} \\ + \\ + &&& \mid \tau \to \tau & \textrm{function type} \\ + &&& \mid \alpha \; ? \; \kappa \to \tau & \textrm{polymorphic function type} \\ + &&& \mid \$ c & \textrm{record type} \\ + \\ + &&& \mid c \; c & \textrm{type-level function application} \\ + &&& \mid \lambda \alpha \; ? \; \kappa \Rightarrow c & \textrm{type-level function abstraction} \\ + \\ + &&& \mid () & \textrm{type-level unit} \\ + &&& \mid \#f & \textrm{field name} \\ + \\ + &&& \mid [c = c, \cdots, c = c] & \textrm{known-length type-level record} \\ + &&& \mid c \rc c & \textrm{type-level record concatenation} \\ + &&& \mid \mt{fold} & \textrm{type-level record fold} \\ + \\ + &&& \mid (c, \cdots, c) & \textrm{type-level tuple} \\ + &&& \mid c.n & \textrm{type-level tuple projection ($n \in \mathbb N^+$)} \\ + \\ + &&& \mid \lambda [c \sim c] \Rightarrow c & \textrm{guarded constructor} \\ + \\ + &&& \mid (c) & \textrm{explicit precedence} \\ +\end{array}$$ + +\end{document} \ No newline at end of file -- cgit v1.2.3 From 6b14029cca03a763f05baf08ce362d8a250b4288 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 27 Nov 2008 14:57:47 -0500 Subject: Signatures --- doc/manual.tex | 73 ++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 25 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 8517206a..e83dc392 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -29,57 +29,80 @@ We give the Ur language definition in \LaTeX $\;$ math mode, since that is prett $\rc$ & \cd{++} \\ \\ $x$ & Normal textual identifier, not beginning with an uppercase letter \\ - $\alpha$ & Normal textual identifier, not beginning with an uppercase letter \\ - $f$ & Normal textual identifier, beginning with an uppercase letter \\ + $X$ & Normal textual identifier, beginning with an uppercase letter \\ \end{tabular} \end{center} -We often write syntax like $N, \cdots, N$ to stand for the non-terminal $N$ repeated 0 or more times. That is, the $\cdots$ symbol is not translated literally to ASCII. +We often write syntax like $e^*$ to indicate zero or more copies of $e$, $e^+$ to indicate one or more copies, and $e,^*$ and $e,^+$ to indicate multiple copies separated by commas. Another separator may be used in place of a comma. The $e$ term may be surrounded by parentheses to indicate grouping; those parentheses should not be included in the actual ASCII. \subsection{Core Syntax} \emph{Kinds} classify types and other compile-time-only entities. Each kind in the grammar is listed with a description of the sort of data it classifies. $$\begin{array}{rrcll} \textrm{Kinds} & \kappa &::=& \mt{Type} & \textrm{proper types} \\ - &&& \mid \mt{Unit} & \textrm{the trivial constructor} \\ - &&& \mid \mt{Name} & \textrm{field names} \\ - &&& \mid \kappa \to \kappa & \textrm{type-level functions} \\ - &&& \mid \{\kappa\} & \textrm{type-level records} \\ - &&& \mid (\kappa \times \cdots \times \kappa) & \textrm{type-level tuples} \\ - &&& \mid (\kappa) & \textrm{explicit precedence} \\ + &&& \mt{Unit} & \textrm{the trivial constructor} \\ + &&& \mt{Name} & \textrm{field names} \\ + &&& \kappa \to \kappa & \textrm{type-level functions} \\ + &&& \{\kappa\} & \textrm{type-level records} \\ + &&& (\kappa\times^+) & \textrm{type-level tuples} \\ + &&& (\kappa) & \textrm{explicit precedence} \\ \end{array}$$ Ur supports several different notions of functions that take types as arguments. These arguments can be either implicit, causing them to be inferred at use sites; or explicit, forcing them to be specified manually at use sites. There is a common explicitness annotation convention applied at the definitions of and in the types of such functions. $$\begin{array}{rrcll} \textrm{Explicitness} & ? &::=& :: & \textrm{explicit} \\ - &&& \mid \; ::: & \textrm{implicit} + &&& \; ::: & \textrm{implicit} \end{array}$$ \emph{Constructors} are the main class of compile-time-only data. They include proper types and are classified by kinds. $$\begin{array}{rrcll} \textrm{Constructors} & c, \tau &::=& (c) :: \kappa & \textrm{kind annotation} \\ - &&& \mid \alpha & \textrm{constructor variable} \\ + &&& x & \textrm{constructor variable} \\ \\ - &&& \mid \tau \to \tau & \textrm{function type} \\ - &&& \mid \alpha \; ? \; \kappa \to \tau & \textrm{polymorphic function type} \\ - &&& \mid \$ c & \textrm{record type} \\ + &&& \tau \to \tau & \textrm{function type} \\ + &&& x \; ? \; \kappa \to \tau & \textrm{polymorphic function type} \\ + &&& \$ c & \textrm{record type} \\ \\ - &&& \mid c \; c & \textrm{type-level function application} \\ - &&& \mid \lambda \alpha \; ? \; \kappa \Rightarrow c & \textrm{type-level function abstraction} \\ + &&& c \; c & \textrm{type-level function application} \\ + &&& \lambda x \; ? \; \kappa \Rightarrow c & \textrm{type-level function abstraction} \\ \\ - &&& \mid () & \textrm{type-level unit} \\ - &&& \mid \#f & \textrm{field name} \\ + &&& () & \textrm{type-level unit} \\ + &&& \#X & \textrm{field name} \\ \\ - &&& \mid [c = c, \cdots, c = c] & \textrm{known-length type-level record} \\ - &&& \mid c \rc c & \textrm{type-level record concatenation} \\ - &&& \mid \mt{fold} & \textrm{type-level record fold} \\ + &&& [(c = c)^*] & \textrm{known-length type-level record} \\ + &&& c \rc c & \textrm{type-level record concatenation} \\ + &&& \mt{fold} & \textrm{type-level record fold} \\ \\ - &&& \mid (c, \cdots, c) & \textrm{type-level tuple} \\ - &&& \mid c.n & \textrm{type-level tuple projection ($n \in \mathbb N^+$)} \\ + &&& (c^+) & \textrm{type-level tuple} \\ + &&& c.n & \textrm{type-level tuple projection ($n \in \mathbb N^+$)} \\ \\ - &&& \mid \lambda [c \sim c] \Rightarrow c & \textrm{guarded constructor} \\ + &&& \lambda [c \sim c] \Rightarrow c & \textrm{guarded constructor} \\ \\ - &&& \mid (c) & \textrm{explicit precedence} \\ + &&& (c) & \textrm{explicit precedence} \\ +\end{array}$$ + +Modules of the module system are described by \emph{signatures}. +$$\begin{array}{rrcll} + \textrm{Signatures} & S &::=& \mt{sig} \; s^* \; \mt{end} & \textrm{constant} \\ + &&& X & \textrm{variable} \\ + &&& \mt{functor}(X : S) : S & \textrm{functor} \\ + &&& S \; \mt{where} \; x = c & \textrm{concretizing an abstract constructor} \\ + &&& M.X & \textrm{projection from a module} \\ + \\ + \textrm{Signature items} & s &::=& \mt{con} \; x :: \kappa & \textrm{abstract constructor} \\ + &&& \mt{con} \; x :: \kappa = c & \textrm{concrete constructor} \\ + &&& \mt{datatype} \; x \; x^* = dc\mid^+ & \textrm{algebraic datatype declaration} \\ + &&& \mt{datatype} \; x = M.x & \textrm{algebraic datatype import} \\ + &&& \mt{val} \; x : \tau & \textrm{value} \\ + &&& \mt{structure} \; X : S & \textrm{sub-module} \\ + &&& \mt{signature} \; X = S & \textrm{sub-signature} \\ + &&& \mt{include} \; S & \textrm{signature inclusion} \\ + &&& \mt{constraint} \; c \sim c & \textrm{record disjointness constraint} \\ + &&& \mt{class} \; x & \textrm{abstract type class} \\ + &&& \mt{class} \; x = c & \textrm{concrete type class} \\ + \\ + \textrm{Datatype constructors} & dc &::=& X & \textrm{nullary constructor} \\ + &&& X \; \mt{of} \; \tau & \textrm{unary constructor} \\ \end{array}$$ \end{document} \ No newline at end of file -- cgit v1.2.3 From a9c2432822c68cfc0897c162b17af6b69d0e22b7 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 27 Nov 2008 15:06:29 -0500 Subject: Patterns --- doc/manual.tex | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index e83dc392..01f5a5f3 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -35,6 +35,8 @@ We give the Ur language definition in \LaTeX $\;$ math mode, since that is prett We often write syntax like $e^*$ to indicate zero or more copies of $e$, $e^+$ to indicate one or more copies, and $e,^*$ and $e,^+$ to indicate multiple copies separated by commas. Another separator may be used in place of a comma. The $e$ term may be surrounded by parentheses to indicate grouping; those parentheses should not be included in the actual ASCII. +We write $\ell$ for literals of the primitive types, for the most part following C conventions. There are $\mt{int}$, $\mt{float}$, and $\mt{string}$ literals. + \subsection{Core Syntax} \emph{Kinds} classify types and other compile-time-only entities. Each kind in the grammar is listed with a description of the sort of data it classifies. @@ -105,4 +107,18 @@ $$\begin{array}{rrcll} &&& X \; \mt{of} \; \tau & \textrm{unary constructor} \\ \end{array}$$ +\emph{Patterns} are used to describe structural conditions on expressions, such that expressions may be tested against patterns, generating assignments to pattern variables if successful. +$$\begin{array}{rrcll} + \textrm{Patterns} & p &::=& \_ & \textrm{wildcard} \\ + &&& x & \textrm{variable} \\ + &&& \ell & \textrm{constant} \\ + &&& \hat{X} & \textrm{nullary constructor} \\ + &&& \hat{X} \; p & \textrm{unary constructor} \\ + &&& \{(x = p,)^*\} & \textrm{rigid record pattern} \\ + &&& \{(x = p,)^+, \ldots\} & \textrm{flexible record pattern} \\ + \\ + \textrm{Qualified capitalized variable} & \hat{X} &::=& X & \textrm{not from a module} \\ + &&& M.X & \textrm{projection from a module} \\ +\end{array}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From 5e3c42711e20b42ba7f850cc5800f01cbfee3f05 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 27 Nov 2008 15:27:17 -0500 Subject: Expressions --- doc/manual.tex | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 01f5a5f3..18879a50 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -5,6 +5,8 @@ \newcommand{\mt}[1]{\mathsf{#1}} \newcommand{\rc}{+ \hspace{-.075in} + \;} +\newcommand{\rcut}{\; \texttt{--} \;} +\newcommand{\rcutM}{\; \texttt{---} \;} \begin{document} @@ -26,7 +28,7 @@ We give the Ur language definition in \LaTeX $\;$ math mode, since that is prett $\times$ & \cd{*} \\ $\lambda$ & \cd{fn} \\ $\Rightarrow$ & \cd{=>} \\ - $\rc$ & \cd{++} \\ + & \cd{---} \\ \\ $x$ & Normal textual identifier, not beginning with an uppercase letter \\ $X$ & Normal textual identifier, beginning with an uppercase letter \\ @@ -37,6 +39,8 @@ We often write syntax like $e^*$ to indicate zero or more copies of $e$, $e^+$ t We write $\ell$ for literals of the primitive types, for the most part following C conventions. There are $\mt{int}$, $\mt{float}$, and $\mt{string}$ literals. +This version of the manual doesn't include operator precedences; see \texttt{src/urweb.grm} for that. + \subsection{Core Syntax} \emph{Kinds} classify types and other compile-time-only entities. Each kind in the grammar is listed with a description of the sort of data it classifies. @@ -47,6 +51,7 @@ $$\begin{array}{rrcll} &&& \kappa \to \kappa & \textrm{type-level functions} \\ &&& \{\kappa\} & \textrm{type-level records} \\ &&& (\kappa\times^+) & \textrm{type-level tuples} \\ + &&& \_ & \textrm{wildcard} \\ &&& (\kappa) & \textrm{explicit precedence} \\ \end{array}$$ @@ -80,6 +85,7 @@ $$\begin{array}{rrcll} \\ &&& \lambda [c \sim c] \Rightarrow c & \textrm{guarded constructor} \\ \\ + &&& \_ & \textrm{wildcard} \\ &&& (c) & \textrm{explicit precedence} \\ \end{array}$$ @@ -116,9 +122,42 @@ $$\begin{array}{rrcll} &&& \hat{X} \; p & \textrm{unary constructor} \\ &&& \{(x = p,)^*\} & \textrm{rigid record pattern} \\ &&& \{(x = p,)^+, \ldots\} & \textrm{flexible record pattern} \\ + &&& (p) & \textrm{explicit precedence} \\ \\ \textrm{Qualified capitalized variable} & \hat{X} &::=& X & \textrm{not from a module} \\ &&& M.X & \textrm{projection from a module} \\ \end{array}$$ +\emph{Expressions} are the main run-time entities, corresponding to both ``expressions'' and ``statements'' in mainstream imperative languages. +$$\begin{array}{rrcll} + \textrm{Expressions} & e &::=& e : \tau & \textrm{type annotation} \\ + &&& x & \textrm{variable} \\ + &&& \ell & \textrm{constant} \\ + \\ + &&& e \; e & \textrm{function application} \\ + &&& \lambda x : \tau \Rightarrow e & \textrm{function abstraction} \\ + &&& e [c] & \textrm{polymorphic function application} \\ + &&& \lambda x \; ? \; \kappa \Rightarrow e & \textrm{polymorphic function abstraction} \\ + \\ + &&& \{(c = e,)^*\} & \textrm{known-length record} \\ + &&& e.c & \textrm{record field projection} \\ + &&& e \rc e & \textrm{record concatenation} \\ + &&& e \rcut c & \textrm{removal of a single record field} \\ + &&& e \rcutM c & \textrm{removal of multiple record fields} \\ + &&& \mt{fold} & \textrm{fold over fields of a type-level record} \\ + \\ + &&& \mt{let} \; ed^* \; \mt{in} \; e \; \mt{end} & \textrm{local definitions} \\ + \\ + &&& \mt{case} \; e \; \mt{of} \; (p \Rightarrow e|)^+ & \textrm{pattern matching} \\ + \\ + &&& \lambda [c \sim c] \Rightarrow e & \textrm{guarded expression} \\ + \\ + &&& \_ & \textrm{wildcard} \\ + &&& (e) & \textrm{explicit precedence} \\ + \\ + \textrm{Local declarations} & ed &::=& \cd{val} \; x : \tau = e & \textrm{non-recursive value} \\ + &&& \cd{val} \; \cd{rec} \; (x : \tau = e \; \cd{and})^+ & \textrm{mutually-recursive values} \\ +\end{array}$$ + + \end{document} \ No newline at end of file -- cgit v1.2.3 From 5f87548c461b829071799d897bd10e5cd4a557a4 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 27 Nov 2008 15:43:10 -0500 Subject: Declarations and modules --- doc/manual.tex | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 18879a50..b1042fdb 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -99,7 +99,7 @@ $$\begin{array}{rrcll} \\ \textrm{Signature items} & s &::=& \mt{con} \; x :: \kappa & \textrm{abstract constructor} \\ &&& \mt{con} \; x :: \kappa = c & \textrm{concrete constructor} \\ - &&& \mt{datatype} \; x \; x^* = dc\mid^+ & \textrm{algebraic datatype declaration} \\ + &&& \mt{datatype} \; x \; x^* = dc\mid^+ & \textrm{algebraic datatype definition} \\ &&& \mt{datatype} \; x = M.x & \textrm{algebraic datatype import} \\ &&& \mt{val} \; x : \tau & \textrm{value} \\ &&& \mt{structure} \; X : S & \textrm{sub-module} \\ @@ -159,5 +159,30 @@ $$\begin{array}{rrcll} &&& \cd{val} \; \cd{rec} \; (x : \tau = e \; \cd{and})^+ & \textrm{mutually-recursive values} \\ \end{array}$$ +\emph{Declarations} primarily bring new symbols into context. +$$\begin{array}{rrcll} + \textrm{Declarations} & d &::=& \mt{con} \; x :: \kappa = c & \textrm{constructor synonym} \\ + &&& \mt{datatype} \; x \; x^* = dc\mid^+ & \textrm{algebraic datatype definition} \\ + &&& \mt{datatype} \; x = M.x & \textrm{algebraic datatype import} \\ + &&& \mt{val} \; x : \tau = e & \textrm{value} \\ + &&& \mt{val} \; \cd{rec} \; (x : \tau = e \; \mt{and})^+ & \textrm{mutually-recursive values} \\ + &&& \mt{structure} \; X : S = M & \textrm{module definition} \\ + &&& \mt{signature} \; X = S & \textrm{signature definition} \\ + &&& \mt{open} \; M & \textrm{module inclusion} \\ + &&& \mt{constraint} \; c \sim c & \textrm{record disjointness constraint} \\ + &&& \mt{open} \; \mt{constraints} \; M & \textrm{inclusion of just the constraints from a module} \\ + &&& \mt{table} \; x : c & \textrm{SQL table} \\ + &&& \mt{sequence} \; x & \textrm{SQL sequence} \\ + &&& \mt{class} \; x = c & \textrm{concrete type class} \\ + &&& \mt{cookie} \; x : c & \textrm{HTTP cookie} \\ + \\ + \textrm{Modules} & M &::=& \mt{struct} \; d^* \; \mt{end} & \mt{constant} \\ + &&& X & \mt{variable} \\ + &&& M.X & \mt{projection} \\ + &&& M(M) & \mt{functor application} \\ + &&& \mt{functor}(X : S) : S = M & \mt{functor abstraction} \\ +\end{array}$$ + +There are two kinds of Ur files. A file named $M\texttt{.ur}$ is an \emph{implementation file}, and it should contain a sequence of declarations $d^*$. A file named $M\texttt{.urs}$ is an \emph{interface file}; it must always have a matching $M\texttt{.ur}$ and should contain a sequence of signature items $s^*$. When both files are present, the overall effect is the same as a monolithic declaration $\mt{structure} \; M : \mt{sig} \; s^* \; \mt{end} = \mt{struct} \; d^* \; \mt{end}$. When no interface file is included, the overall effect is similar, with a signature for module $M$ being inferred rather than just checked against an interface. \end{document} \ No newline at end of file -- cgit v1.2.3 From e5d50c25383c90543455c6977270c3a675f888d4 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 27 Nov 2008 16:55:30 -0500 Subject: Shorthands --- doc/manual.tex | 74 ++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 15 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index b1042fdb..9a2f4173 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -15,7 +15,9 @@ \maketitle -\section{Syntax} +\section{Ur Syntax} + +In this section, we describe the syntax of Ur, deferring to a later section discussion of most of the syntax specific to SQL and XML. The sole exceptions are the declaration forms for tables, sequences, and cookies. \subsection{Lexical Conventions} @@ -28,7 +30,9 @@ We give the Ur language definition in \LaTeX $\;$ math mode, since that is prett $\times$ & \cd{*} \\ $\lambda$ & \cd{fn} \\ $\Rightarrow$ & \cd{=>} \\ - & \cd{---} \\ + $\neq$ & \cd{<>} \\ + $\leq$ & \cd{<=} \\ + $\geq$ & \cd{>=} \\ \\ $x$ & Normal textual identifier, not beginning with an uppercase letter \\ $X$ & Normal textual identifier, beginning with an uppercase letter \\ @@ -51,7 +55,7 @@ $$\begin{array}{rrcll} &&& \kappa \to \kappa & \textrm{type-level functions} \\ &&& \{\kappa\} & \textrm{type-level records} \\ &&& (\kappa\times^+) & \textrm{type-level tuples} \\ - &&& \_ & \textrm{wildcard} \\ + &&& \_\_ & \textrm{wildcard} \\ &&& (\kappa) & \textrm{explicit precedence} \\ \end{array}$$ @@ -85,7 +89,7 @@ $$\begin{array}{rrcll} \\ &&& \lambda [c \sim c] \Rightarrow c & \textrm{guarded constructor} \\ \\ - &&& \_ & \textrm{wildcard} \\ + &&& \_ :: \kappa & \textrm{wildcard} \\ &&& (c) & \textrm{explicit precedence} \\ \end{array}$$ @@ -94,13 +98,13 @@ $$\begin{array}{rrcll} \textrm{Signatures} & S &::=& \mt{sig} \; s^* \; \mt{end} & \textrm{constant} \\ &&& X & \textrm{variable} \\ &&& \mt{functor}(X : S) : S & \textrm{functor} \\ - &&& S \; \mt{where} \; x = c & \textrm{concretizing an abstract constructor} \\ + &&& S \; \mt{where} \; \mt{con} \; x = c & \textrm{concretizing an abstract constructor} \\ &&& M.X & \textrm{projection from a module} \\ \\ \textrm{Signature items} & s &::=& \mt{con} \; x :: \kappa & \textrm{abstract constructor} \\ &&& \mt{con} \; x :: \kappa = c & \textrm{concrete constructor} \\ &&& \mt{datatype} \; x \; x^* = dc\mid^+ & \textrm{algebraic datatype definition} \\ - &&& \mt{datatype} \; x = M.x & \textrm{algebraic datatype import} \\ + &&& \mt{datatype} \; x = \mt{datatype} \; M.x & \textrm{algebraic datatype import} \\ &&& \mt{val} \; x : \tau & \textrm{value} \\ &&& \mt{structure} \; X : S & \textrm{sub-module} \\ &&& \mt{signature} \; X = S & \textrm{sub-signature} \\ @@ -124,14 +128,15 @@ $$\begin{array}{rrcll} &&& \{(x = p,)^+, \ldots\} & \textrm{flexible record pattern} \\ &&& (p) & \textrm{explicit precedence} \\ \\ - \textrm{Qualified capitalized variable} & \hat{X} &::=& X & \textrm{not from a module} \\ + \textrm{Qualified capitalized variables} & \hat{X} &::=& X & \textrm{not from a module} \\ &&& M.X & \textrm{projection from a module} \\ \end{array}$$ \emph{Expressions} are the main run-time entities, corresponding to both ``expressions'' and ``statements'' in mainstream imperative languages. $$\begin{array}{rrcll} \textrm{Expressions} & e &::=& e : \tau & \textrm{type annotation} \\ - &&& x & \textrm{variable} \\ + &&& \hat{x} & \textrm{variable} \\ + &&& \hat{X} & \textrm{datatype constructor} \\ &&& \ell & \textrm{constant} \\ \\ &&& e \; e & \textrm{function application} \\ @@ -157,13 +162,16 @@ $$\begin{array}{rrcll} \\ \textrm{Local declarations} & ed &::=& \cd{val} \; x : \tau = e & \textrm{non-recursive value} \\ &&& \cd{val} \; \cd{rec} \; (x : \tau = e \; \cd{and})^+ & \textrm{mutually-recursive values} \\ + \\ + \textrm{Qualified uncapitalized variables} & \hat{x} &::=& x & \textrm{not from a module} \\ + &&& M.x & \textrm{projection from a module} \\ \end{array}$$ \emph{Declarations} primarily bring new symbols into context. $$\begin{array}{rrcll} \textrm{Declarations} & d &::=& \mt{con} \; x :: \kappa = c & \textrm{constructor synonym} \\ &&& \mt{datatype} \; x \; x^* = dc\mid^+ & \textrm{algebraic datatype definition} \\ - &&& \mt{datatype} \; x = M.x & \textrm{algebraic datatype import} \\ + &&& \mt{datatype} \; x = \mt{datatype} \; M.x & \textrm{algebraic datatype import} \\ &&& \mt{val} \; x : \tau = e & \textrm{value} \\ &&& \mt{val} \; \cd{rec} \; (x : \tau = e \; \mt{and})^+ & \textrm{mutually-recursive values} \\ &&& \mt{structure} \; X : S = M & \textrm{module definition} \\ @@ -174,15 +182,51 @@ $$\begin{array}{rrcll} &&& \mt{table} \; x : c & \textrm{SQL table} \\ &&& \mt{sequence} \; x & \textrm{SQL sequence} \\ &&& \mt{class} \; x = c & \textrm{concrete type class} \\ - &&& \mt{cookie} \; x : c & \textrm{HTTP cookie} \\ + &&& \mt{cookie} \; x : \tau & \textrm{HTTP cookie} \\ \\ - \textrm{Modules} & M &::=& \mt{struct} \; d^* \; \mt{end} & \mt{constant} \\ - &&& X & \mt{variable} \\ - &&& M.X & \mt{projection} \\ - &&& M(M) & \mt{functor application} \\ - &&& \mt{functor}(X : S) : S = M & \mt{functor abstraction} \\ + \textrm{Modules} & M &::=& \mt{struct} \; d^* \; \mt{end} & \textrm{constant} \\ + &&& X & \textrm{variable} \\ + &&& M.X & \textrm{projection} \\ + &&& M(M) & \textrm{functor application} \\ + &&& \mt{functor}(X : S) : S = M & \textrm{functor abstraction} \\ \end{array}$$ There are two kinds of Ur files. A file named $M\texttt{.ur}$ is an \emph{implementation file}, and it should contain a sequence of declarations $d^*$. A file named $M\texttt{.urs}$ is an \emph{interface file}; it must always have a matching $M\texttt{.ur}$ and should contain a sequence of signature items $s^*$. When both files are present, the overall effect is the same as a monolithic declaration $\mt{structure} \; M : \mt{sig} \; s^* \; \mt{end} = \mt{struct} \; d^* \; \mt{end}$. When no interface file is included, the overall effect is similar, with a signature for module $M$ being inferred rather than just checked against an interface. +\subsection{Shorthands} + +There are a variety of derived syntactic forms that elaborate into the core syntax from the last subsection. We will present the additional forms roughly following the order in which we presented the constructs that they elaborate into. + +In many contexts where record fields are expected, like in a projection $e.c$, a constant field may be written as simply $X$, rather than $\#X$. + +A record type may be written $\{(c = c,)^*\}$, which elaborates to $\$[(c = c,)^*]$. + +A tuple type $(\tau_1, \ldots, \tau_n)$ expands to a record type $\{1 = \tau_1, \ldots, n = \tau_n\}$, with natural numbers as field names. A tuple pattern $(p_1, \ldots, p_n)$ expands to a rigid record pattern $\{1 = p_1, \ldots, n = p_n\}$. Positive natural numbers may be used in most places where field names would be allowed. + +In general, several adjacent $\lambda$ forms may be combined into one, and kind and type annotations may be omitted, in which case they are implicitly included as wildcards. More formally, for constructor-level abstractions, we can define a new non-terminal $b ::= x \mid (x :: \kappa) \mid [c \sim c]$ and allow composite abstractions of the form $\lambda b^+ \Rightarrow c$, elaborating into the obvious sequence of one core $\lambda$ per element of $b^+$. + +For any signature item or declaration that defines some entity to be equal to $A$ with classification annotation $B$ (e.g., $\mt{val} \; x : B = A$), $B$ and the preceding colon (or similar punctuation) may be omitted, in which case it is filled in as a wildcard. + +A signature item or declaration $\mt{type} \; x$ or $\mt{type} \; x = \tau$ is elaborated into $\mt{con} \; x :: \mt{Type}$ or $\mt{con} \; x :: \mt{Type} = \tau$, respectively. + +A signature item or declaration $\mt{class} \; x = \lambda y :: \mt{Type} \Rightarrow c$ may be abbreviated $\mt{class} \; x \; y = c$. + +Handling of implicit and explicit constructor arguments may be tweaked with some prefixes to variable references. An expression $@x$ is a version of $x$ where all implicit constructor arguments have been made explicit. An expression $@@x$ achieves the same effect, additionally halting automatic resolution of type class instances. The same syntax works for variables projected out of modules and for capitalized variables (datatype constructors). + +At the expression level, an analogue is available of the composite $\lambda$ form for constructors. We define the language of binders as $b ::= x \mid (x : \tau) \mid (x \; ? \; \kappa) \mid [c \sim c]$. A lone variable $x$ as a binder stands for an expression variable of unspecified type. + +A $\mt{val}$ or $\mt{val} \; \mt{rec}$ declaration may include expression binders before the equal sign, following the binder grammar from the last paragraph. Such declarations are elaborated into versions that add additional $\lambda$s to the fronts of the righthand sides, as appropriate. The keyword $\mt{fun}$ is a synonym for $\mt{val} \; \mt{rec}$. + +A signature item $\mt{functor} \; X_1 \; (X_2 : S_1) : S_2$ is elaborated into $\mt{structure} \; X_1 : \mt{functor}(X_2 : S_1) : S_2$. A declaration $\mt{functor} \; X_1 \; (X_2 : S_1) : S_2 = M$ is elaborated into $\mt{structure} \; X_1 : \mt{functor}(X_2 : S_1) : S_2 = \mt{functor}(X_2 : S_1) : S_2 = M$. + +A declaration $\mt{table} \; x : \{(c = c,)^*\}$ is elaborated into $\mt{table} \; x : [(c = c,)^*]$ + +The syntax $\mt{where} \; \mt{type}$ is an alternate form of $\mt{where} \; \mt{con}$. + +The syntax $\mt{if} \; e \; \mt{then} \; e_1 \; \mt{else} \; e_2$ expands to $\mt{case} \; e \; \mt{of} \; \mt{Basis}.\mt{True} \Rightarrow e_1 \mid \mt{Basis}.\mt{False} \Rightarrow e_2$. + +There are infix operator syntaxes for a number of functions defined in the $\mt{Basis}$ module. There is $=$ for $\mt{eq}$, $\neq$ for $\mt{neq}$, $-$ for $\mt{neg}$ (as a prefix operator) and $\mt{minus}$, $+$ for $\mt{plus}$, $\times$ for $\mt{times}$, $/$ for $\mt{div}$, $\%$ for $\mt{mod}$, $<$ for $\mt{lt}$, $\leq$ for $\mt{le}$, $>$ for $\mt{gt}$, and $\geq$ for $\mt{ge}$. + +A signature item $\mt{table} \; x : c$ is shorthand for $\mt{val} \; x : \mt{Basis}.\mt{sql\_table} \; c$. $\mt{sequence} \; x$ is short for $\mt{val} \; x : \mt{Basis}.\mt{sql\_sequence}$, and $\mt{cookie} \; x : \tau$ is shorthand for $\mt{val} \; x : \mt{Basis}.\mt{http\_cookie} \; \tau$. + \end{document} \ No newline at end of file -- cgit v1.2.3 From 413a2ddcfcbf235bf0cdd220f7ecefe93db37bf0 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 29 Nov 2008 09:34:11 -0500 Subject: Kinding --- doc/manual.tex | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 5 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 9a2f4173..0bd129cd 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -68,14 +68,14 @@ $$\begin{array}{rrcll} \emph{Constructors} are the main class of compile-time-only data. They include proper types and are classified by kinds. $$\begin{array}{rrcll} \textrm{Constructors} & c, \tau &::=& (c) :: \kappa & \textrm{kind annotation} \\ - &&& x & \textrm{constructor variable} \\ + &&& \hat{x} & \textrm{constructor variable} \\ \\ &&& \tau \to \tau & \textrm{function type} \\ &&& x \; ? \; \kappa \to \tau & \textrm{polymorphic function type} \\ &&& \$ c & \textrm{record type} \\ \\ &&& c \; c & \textrm{type-level function application} \\ - &&& \lambda x \; ? \; \kappa \Rightarrow c & \textrm{type-level function abstraction} \\ + &&& \lambda x \; :: \; \kappa \Rightarrow c & \textrm{type-level function abstraction} \\ \\ &&& () & \textrm{type-level unit} \\ &&& \#X & \textrm{field name} \\ @@ -91,6 +91,9 @@ $$\begin{array}{rrcll} \\ &&& \_ :: \kappa & \textrm{wildcard} \\ &&& (c) & \textrm{explicit precedence} \\ + \\ + \textrm{Qualified uncapitalized variables} & \hat{x} &::=& x & \textrm{not from a module} \\ + &&& M.x & \textrm{projection from a module} \\ \end{array}$$ Modules of the module system are described by \emph{signatures}. @@ -162,9 +165,6 @@ $$\begin{array}{rrcll} \\ \textrm{Local declarations} & ed &::=& \cd{val} \; x : \tau = e & \textrm{non-recursive value} \\ &&& \cd{val} \; \cd{rec} \; (x : \tau = e \; \cd{and})^+ & \textrm{mutually-recursive values} \\ - \\ - \textrm{Qualified uncapitalized variables} & \hat{x} &::=& x & \textrm{not from a module} \\ - &&& M.x & \textrm{projection from a module} \\ \end{array}$$ \emph{Declarations} primarily bring new symbols into context. @@ -229,4 +229,89 @@ There are infix operator syntaxes for a number of functions defined in the $\mt{ A signature item $\mt{table} \; x : c$ is shorthand for $\mt{val} \; x : \mt{Basis}.\mt{sql\_table} \; c$. $\mt{sequence} \; x$ is short for $\mt{val} \; x : \mt{Basis}.\mt{sql\_sequence}$, and $\mt{cookie} \; x : \tau$ is shorthand for $\mt{val} \; x : \mt{Basis}.\mt{http\_cookie} \; \tau$. + +\section{Static Semantics} + +In this section, we give a declarative presentation of Ur's typing rules and related judgments. Inference is the subject of the next section; here, we assume that an oracle has filled in all wildcards with concrete values. + +Since there is significant mutual recursion among the judgments, we introduce them all before beginning to give rules. We use the same variety of contexts throughout this section, implicitly introducing new sorts of context entries as needed. +\begin{itemize} +\item $\Gamma \vdash c :: \kappa$ assigns a kind to a constructor in a context. +\item $\Gamma \vdash c \sim c$ proves the disjointness of two record constructors; that is, that they share no field names. We overload the judgment to apply to pairs of field names as well. +\item $\Gamma \vdash c \hookrightarrow \overline{c}$ proves that record constructor $c$ decomposes into set $\overline{c}$ of field names and record constructors. +\item $\Gamma \vdash c \equiv c$ proves the computational equivalence of two constructors. This is often called a \emph{definitional equality} in the world of type theory. +\item $\Gamma \vdash e : \tau$ is a standard typing judgment. +\item $\Gamma \vdash M : S$ is the module signature checking judgment. +\item $\mt{proj}(M, S, V)$ is a partial function for projecting a signature item from a signature $S$, given the module $M$ that we project from. $V$ may be $\mt{con} \; x$, $\mt{val} \; x$, $\mt{signature} \; X$, or $\mt{structure} \; X$. The parameter $M$ is needed because the projected signature item may refer to other items of $S$. +\end{itemize} + +\subsection{Kinding} + +$$\infer{\Gamma \vdash (c) :: \kappa :: \kappa}{ + \Gamma \vdash c :: \kappa +} +\quad \infer{\Gamma \vdash x :: \kappa}{ + x :: \kappa \in \Gamma +} +\quad \infer{\Gamma \vdash x :: \kappa}{ + x :: \kappa = c \in \Gamma +}$$ + +$$\infer{\Gamma \vdash M.x :: \kappa}{ + \Gamma \vdash M : S + & \mt{proj}(M, S, \mt{con} \; x) = \kappa +} +\quad \infer{\Gamma \vdash M.x :: \kappa}{ + \Gamma \vdash M : S + & \mt{proj}(M, S, \mt{con} \; x) = (\kappa, c) +}$$ + +$$\infer{\Gamma \vdash \tau_1 \to \tau_2 :: \mt{Type}}{ + \Gamma \vdash \tau_1 :: \mt{Type} + & \Gamma \vdash \tau_2 :: \mt{Type} +} +\quad \infer{\Gamma \vdash x \; ? \: \kappa \to \tau :: \mt{Type}}{ + \Gamma, x :: \kappa \vdash \tau :: \mt{Type} +} +\quad \infer{\Gamma \vdash \$c :: \mt{Type}}{ + \Gamma \vdash c :: \{\mt{Type}\} +}$$ + +$$\infer{\Gamma \vdash c_1 \; c_2 :: \kappa_2}{ + \Gamma \vdash c_1 :: \kappa_1 \to \kappa_2 + & \Gamma \vdash c_2 :: \kappa_1 +} +\quad \infer{\Gamma \vdash \lambda x \; :: \; \kappa_1 \Rightarrow c :: \kappa_1 \to \kappa_2}{ + \Gamma, x :: \kappa_1 \vdash c :: \kappa_2 +}$$ + +$$\infer{\Gamma \vdash () :: \mt{Unit}}{} +\quad \infer{\Gamma \vdash \#X :: \mt{Name}}{}$$ + +$$\infer{\Gamma \vdash [\overline{c_i = c'_i}] :: \{\kappa\}}{ + \forall i: \Gamma \vdash c_i : \mt{Name} + & \Gamma \vdash c'_i :: \kappa + & \forall i \neq j: \Gamma \vdash c_i \sim c_j +} +\quad \infer{\Gamma \vdash c_1 \rc c_2 :: \{\kappa\}}{ + \Gamma \vdash c_1 :: \{\kappa\} + & \Gamma \vdash c_2 :: \{\kappa\} + & \Gamma \vdash c_1 \sim c_2 +}$$ + +$$\infer{\Gamma \vdash \mt{fold} :: ((\mt{Name} \to \kappa_1 \to \kappa_2 \to \kappa_2) \to \kappa_2 \to \{\kappa_1\} \to \kappa_2}{}$$ + +$$\infer{\Gamma \vdash (\overline c) :: (k_1 \times \ldots \times k_n)}{ + \forall i: \Gamma \vdash c_i :: k_i +} +\quad \infer{\Gamma \vdash c.i :: k_i}{ + \Gamma \vdash c :: (k_1 \times \ldots \times k_n) +}$$ + +$$\infer{\Gamma \vdash \lambda [c_1 \sim c_2] \Rightarrow c :: \kappa}{ + \Gamma \vdash c_1 :: \{\kappa'\} + & \Gamma \vdash c_2 :: \{\kappa'\} + & \Gamma, c_1 \sim c_2 \vdash c :: \kappa +}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From 96c1d0efd00362926493295a132c19a209ac7838 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 29 Nov 2008 09:48:10 -0500 Subject: Disjointness --- doc/manual.tex | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 0bd129cd..2b0f2c57 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -238,7 +238,7 @@ Since there is significant mutual recursion among the judgments, we introduce th \begin{itemize} \item $\Gamma \vdash c :: \kappa$ assigns a kind to a constructor in a context. \item $\Gamma \vdash c \sim c$ proves the disjointness of two record constructors; that is, that they share no field names. We overload the judgment to apply to pairs of field names as well. -\item $\Gamma \vdash c \hookrightarrow \overline{c}$ proves that record constructor $c$ decomposes into set $\overline{c}$ of field names and record constructors. +\item $\Gamma \vdash c \hookrightarrow C$ proves that record constructor $c$ decomposes into set $C$ of field names and record constructors. \item $\Gamma \vdash c \equiv c$ proves the computational equivalence of two constructors. This is often called a \emph{definitional equality} in the world of type theory. \item $\Gamma \vdash e : \tau$ is a standard typing judgment. \item $\Gamma \vdash M : S$ is the module signature checking judgment. @@ -314,4 +314,40 @@ $$\infer{\Gamma \vdash \lambda [c_1 \sim c_2] \Rightarrow c :: \kappa}{ & \Gamma, c_1 \sim c_2 \vdash c :: \kappa }$$ +\subsection{Record Disjointness} + +We will use a keyword $\mt{map}$ as a shorthand, such that, for $f$ of kind $\kappa \to \kappa'$, $\mt{map} \; f$ stands for $\mt{fold} \; (\lambda (x_1 :: \mt{Name}) (x_2 :: \kappa) (x_3 :: \{\kappa'\}) \Rightarrow [x_1 = f \; x_2] \rc x_3) \; []$. + +$$\infer{\Gamma \vdash c_1 \sim c_2}{ + \Gamma \vdash c_1 \hookrightarrow c'_1 + & \Gamma \vdash c_2 \hookrightarrow c'_2 + & \forall c''_1 \in c'_1, c''_2 \in c'_2: \Gamma \vdash c''_1 \sim c''_2 +} +\quad \infer{\Gamma \vdash X \sim X'}{ + X \neq X' +}$$ + +$$\infer{\Gamma \vdash c_1 \sim c_2}{ + c'_1 \sim c'_2 \in \Gamma + & \Gamma \vdash c'_1 \hookrightarrow c''_1 + & \Gamma \vdash c'_2 \hookrightarrow c''_2 + & c_1 \in c''_1 + & c_2 \in c''_2 +}$$ + +$$\infer{\Gamma \vdash c \hookrightarrow \{c\}}{} +\quad \infer{\Gamma \vdash [\overline{c = c'}] \hookrightarrow \{\overline{c}\}}{} +\quad \infer{\Gamma \vdash c_1 \rc c_2 \hookrightarrow C_1 \cup C_2}{ + \Gamma \vdash c_1 \hookrightarrow C_1 + & \Gamma \vdash c_2 \hookrightarrow C_2 +} +\quad \infer{\Gamma \vdash c \hookrightarrow C}{ + \Gamma \vdash c \equiv c' + & \Gamma \vdash c' \hookrightarrow C +} +\quad \infer{\Gamma \vdash \mt{map} \; f \; c \hookrightarrow C}{ + \Gamma \vdash c \hookrightarrow C +}$$ + + \end{document} \ No newline at end of file -- cgit v1.2.3 From e4fff6ca5e4e4d1e6a4dba3456a002e4f6bc3e2d Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 29 Nov 2008 10:05:46 -0500 Subject: Definitional equality --- doc/manual.tex | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 2b0f2c57..cff270df 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -349,5 +349,48 @@ $$\infer{\Gamma \vdash c \hookrightarrow \{c\}}{} \Gamma \vdash c \hookrightarrow C }$$ +\subsection{Definitional Equality} + +We use $\mathcal C$ to stand for a one-hole context that, when filled, yields a constructor. The notation $\mathcal C[c]$ plugs $c$ into $\mathcal C$. We omit the standard definition of one-hole contexts. We write $[x \mapsto c_1]c_2$ for capture-avoiding substitution of $c_1$ for $x$ in $c_2$. + +$$\infer{\Gamma \vdash c \equiv c}{} +\quad \infer{\Gamma \vdash c_1 \equiv c_2}{ + \Gamma \vdash c_2 \equiv c_1 +} +\quad \infer{\Gamma \vdash c_1 \equiv c_3}{ + \Gamma \vdash c_1 \equiv c_2 + & \Gamma \vdash c_2 \equiv c_3 +} +\quad \infer{\Gamma \vdash \mathcal C[c_1] \equiv \mathcal C[c_2]}{ + \Gamma \vdash c_1 \equiv c_2 +}$$ + +$$\infer{\Gamma \vdash x \equiv c}{ + x :: \kappa = c \in \Gamma +} +\quad \infer{\Gamma \vdash M.x \equiv c}{ + \Gamma \vdash M : S + & \mt{proj}(M, S, \mt{con} \; x) = (\kappa, c) +} +\quad \infer{\Gamma \vdash (\overline c).i \equiv c_i}{}$$ + +$$\infer{\Gamma \vdash (\lambda x :: \kappa \Rightarrow c) \; c' \equiv [x \mapsto c'] c}{} +\quad \infer{\Gamma \vdash c_1 \rc c_2 \equiv c_2 \rc c_1}{} +\quad \infer{\Gamma \vdash c_1 \rc (c_2 \rc c_3) \equiv (c_1 \rc c_2) \rc c_3}{}$$ + +$$\infer{\Gamma \vdash [] \rc c \equiv c}{} +\quad \infer{\Gamma \vdash [\overline{c_1 = c'_1}] \rc [\overline{c_2 = c'_2}] \equiv [\overline{c_1 = c'_1}, \overline{c_2 = c'_2}]}{}$$ + +$$\infer{\Gamma \vdash \lambda [c_1 \sim c_2] \Rightarrow c \equiv c}{ + \Gamma \vdash c_1 \sim c_2 +} +\quad \infer{\Gamma \vdash \mt{fold} \; f \; i \; [] \equiv i}{} +\quad \infer{\Gamma \vdash \mt{fold} \; f \; i \; ([c_1 = c_2] \rc c) \equiv f \; c_1 \; c_2 \; (\mt{fold} \; f \; i \; c)}{}$$ + +$$\infer{\Gamma \vdash \mt{map} \; (\lambda x \Rightarrow x) \; c \equiv c}{} +\quad \infer{\Gamma \vdash \mt{fold} \; f \; i \; (\mt{map} \; f' \; c) + \equiv \mt{fold} \; (\lambda (x_1 :: \mt{Name}) (x_2 :: \kappa) \Rightarrow f \; x_1 \; (f' \; x_2)) \; i \; c}{}$$ + +$$\infer{\Gamma \vdash \mt{map} \; f \; (c_1 \rc c_2) \equiv \mt{map} \; f \; c_1 \rc \mt{map} \; f \; c_2}{}$$ \end{document} \ No newline at end of file -- cgit v1.2.3 From 6748925a8c158e84a40b2e8f0142eaea7691d2f6 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 29 Nov 2008 10:34:56 -0500 Subject: Typing --- doc/manual.tex | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index cff270df..dec14cd2 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -201,6 +201,8 @@ In many contexts where record fields are expected, like in a projection $e.c$, a A record type may be written $\{(c = c,)^*\}$, which elaborates to $\$[(c = c,)^*]$. +The notation $[c_1, \ldots, c_n]$ is shorthand for $[c_1 = (), \ldots, c_n = ()]$. + A tuple type $(\tau_1, \ldots, \tau_n)$ expands to a record type $\{1 = \tau_1, \ldots, n = \tau_n\}$, with natural numbers as field names. A tuple pattern $(p_1, \ldots, p_n)$ expands to a rigid record pattern $\{1 = p_1, \ldots, n = p_n\}$. Positive natural numbers may be used in most places where field names would be allowed. In general, several adjacent $\lambda$ forms may be combined into one, and kind and type annotations may be omitted, in which case they are implicitly included as wildcards. More formally, for constructor-level abstractions, we can define a new non-terminal $b ::= x \mid (x :: \kappa) \mid [c \sim c]$ and allow composite abstractions of the form $\lambda b^+ \Rightarrow c$, elaborating into the obvious sequence of one core $\lambda$ per element of $b^+$. @@ -241,6 +243,8 @@ Since there is significant mutual recursion among the judgments, we introduce th \item $\Gamma \vdash c \hookrightarrow C$ proves that record constructor $c$ decomposes into set $C$ of field names and record constructors. \item $\Gamma \vdash c \equiv c$ proves the computational equivalence of two constructors. This is often called a \emph{definitional equality} in the world of type theory. \item $\Gamma \vdash e : \tau$ is a standard typing judgment. +\item $\Gamma \vdash p \leadsto \Gamma, \tau$ combines typing of patterns with calculation of which new variables they bind. +\item $\Gamma \vdash d \leadsto \Gamma$ expresses how a declaration modifies a context. We overload this judgment to apply to sequences of declarations. \item $\Gamma \vdash M : S$ is the module signature checking judgment. \item $\mt{proj}(M, S, V)$ is a partial function for projecting a signature item from a signature $S$, given the module $M$ that we project from. $V$ may be $\mt{con} \; x$, $\mt{val} \; x$, $\mt{signature} \; X$, or $\mt{structure} \; X$. The parameter $M$ is needed because the projected signature item may refer to other items of $S$. \end{itemize} @@ -393,4 +397,92 @@ $$\infer{\Gamma \vdash \mt{map} \; (\lambda x \Rightarrow x) \; c \equiv c}{} $$\infer{\Gamma \vdash \mt{map} \; f \; (c_1 \rc c_2) \equiv \mt{map} \; f \; c_1 \rc \mt{map} \; f \; c_2}{}$$ +\subsection{Typing} + +We assume the existence of a function $T$ assigning types to literal constants. It maps integer constants to $\mt{Basis}.\mt{int}$, float constants to $\mt{Basis}.\mt{float}$, and string constants to $\mt{Basis}.\mt{string}$. + +We also refer to a function $\mathcal I$, such that $\mathcal I(\tau)$ ``uses an oracle'' to instantiate all constructor function arguments at the beginning of $\tau$ that are marked implicit; i.e., replace $x_1 ::: \kappa_1 \to \ldots \to x_n ::: \kappa_n \to \tau$ with $[x_1 \mapsto c_1]\ldots[x_n \mapsto c_n]\tau$, where the $c_i$s are inferred and $\tau$ does not start like $x ::: \kappa \to \tau'$. + +$$\infer{\Gamma \vdash e : \tau : \tau}{ + \Gamma \vdash e : \tau +} +\quad \infer{\Gamma \vdash e : \tau}{ + \Gamma \vdash e : \tau' + & \Gamma \vdash \tau' \equiv \tau +} +\quad \infer{\Gamma \vdash \ell : T(\ell)}{}$$ + +$$\infer{\Gamma \vdash x : \mathcal I(\tau)}{ + x : \tau \in \Gamma +} +\quad \infer{\Gamma \vdash M.x : \mathcal I(\tau)}{ + \Gamma \vdash M : S + & \mt{proj}(M, S, \mt{val} \; x) = \tau +} +\quad \infer{\Gamma \vdash X : \mathcal I(\tau)}{ + X : \tau \in \Gamma +} +\quad \infer{\Gamma \vdash M.X : \mathcal I(\tau)}{ + \Gamma \vdash M : S + & \mt{proj}(M, S, \mt{val} \; X) = \tau +}$$ + +$$\infer{\Gamma \vdash e_1 \; e_2 : \tau_2}{ + \Gamma \vdash e_1 : \tau_1 \to \tau_2 + & \Gamma \vdash e_2 : \tau_1 +} +\quad \infer{\Gamma \vdash \lambda x : \tau_1 \Rightarrow e : \tau_1 \to \tau_2}{ + \Gamma, x : \tau_1 \vdash e : \tau_2 +}$$ + +$$\infer{\Gamma \vdash e [c] : [x \mapsto c]\tau}{ + \Gamma \vdash e : x :: \kappa \to \tau + & \Gamma \vdash c :: \kappa +} +\quad \infer{\Gamma \vdash \lambda x \; ? \; \kappa \Rightarrow e : x \; ? \; \kappa \to \tau}{ + \Gamma, x :: \kappa \vdash e : \tau +}$$ + +$$\infer{\Gamma \vdash \{\overline{c = e}\} : \{\overline{c : \tau}\}}{ + \forall i: \Gamma \vdash c_i :: \mt{Name} + & \Gamma \vdash e_i : \tau_i + & \forall i \neq j: \Gamma \vdash c_i \sim c_j +} +\quad \infer{\Gamma \vdash e.c : \tau}{ + \Gamma \vdash e : \$([c = \tau] \rc c') +} +\quad \infer{\Gamma \vdash e_1 \rc e_2 : \$(c_1 \rc c_2)}{ + \Gamma \vdash e_1 : \$c_1 + & \Gamma \vdash e_2 : \$c_2 +}$$ + +$$\infer{\Gamma \vdash e \rcut c : \$c'}{ + \Gamma \vdash e : \$([c = \tau] \rc c') +} +\quad \infer{\Gamma \vdash e \rcutM c : \$c'}{ + \Gamma \vdash e : \$(c \rc c') +}$$ + +$$\infer{\Gamma \vdash \mt{fold} : \begin{array}{c} + x_1 :: (\{\kappa\} \to \tau) + \to (x_2 :: \mt{Name} \to x_3 :: \kappa \to x_4 :: \{\kappa\} \to \lambda [[x_2] \sim x_4] + \Rightarrow x_1 \; x_4 \to x_1 \; ([x_2 = x_3] \rc x_4)) \\ + \to x_1 \; [] \to x_5 :: \{\kappa\} \to x_1 \; x_5 + \end{array}}{}$$ + +$$\infer{\Gamma \vdash \mt{let} \; \overline{ed} \; \mt{in} \; e \; \mt{end} : \tau}{ + \Gamma \vdash \overline{ed} \leadsto \Gamma' + & \Gamma' \vdash e : \tau +} +\quad \infer{\Gamma \vdash \mt{case} \; e \; \mt{of} \; \overline{p \Rightarrow e} : \tau}{ + \forall i: \Gamma \vdash p_i \leadsto \Gamma_i, \tau' + & \Gamma_i \vdash e_i : \tau +}$$ + +$$\infer{\Gamma \vdash [c_1 \sim c_2] \Rightarrow e : [c_1 \sim c_2] \Rightarrow \tau}{ + \Gamma \vdash c_1 :: \{\kappa\} + & \Gamma \vdash c_2 :: \{\kappa\} + & \Gamma, c_1 \sim c_2 \vdash e : \tau +}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From 73de524554aaa11c454e95cec39e8ada98c44cf4 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 29 Nov 2008 10:49:47 -0500 Subject: Pattern typing --- doc/manual.tex | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index dec14cd2..db679405 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -243,7 +243,7 @@ Since there is significant mutual recursion among the judgments, we introduce th \item $\Gamma \vdash c \hookrightarrow C$ proves that record constructor $c$ decomposes into set $C$ of field names and record constructors. \item $\Gamma \vdash c \equiv c$ proves the computational equivalence of two constructors. This is often called a \emph{definitional equality} in the world of type theory. \item $\Gamma \vdash e : \tau$ is a standard typing judgment. -\item $\Gamma \vdash p \leadsto \Gamma, \tau$ combines typing of patterns with calculation of which new variables they bind. +\item $\Gamma \vdash p \leadsto \Gamma; \tau$ combines typing of patterns with calculation of which new variables they bind. \item $\Gamma \vdash d \leadsto \Gamma$ expresses how a declaration modifies a context. We overload this judgment to apply to sequences of declarations. \item $\Gamma \vdash M : S$ is the module signature checking judgment. \item $\mt{proj}(M, S, V)$ is a partial function for projecting a signature item from a signature $S$, given the module $M$ that we project from. $V$ may be $\mt{con} \; x$, $\mt{val} \; x$, $\mt{signature} \; X$, or $\mt{structure} \; X$. The parameter $M$ is needed because the projected signature item may refer to other items of $S$. @@ -397,7 +397,7 @@ $$\infer{\Gamma \vdash \mt{map} \; (\lambda x \Rightarrow x) \; c \equiv c}{} $$\infer{\Gamma \vdash \mt{map} \; f \; (c_1 \rc c_2) \equiv \mt{map} \; f \; c_1 \rc \mt{map} \; f \; c_2}{}$$ -\subsection{Typing} +\subsection{Expression Typing} We assume the existence of a function $T$ assigning types to literal constants. It maps integer constants to $\mt{Basis}.\mt{int}$, float constants to $\mt{Basis}.\mt{float}$, and string constants to $\mt{Basis}.\mt{string}$. @@ -485,4 +485,40 @@ $$\infer{\Gamma \vdash [c_1 \sim c_2] \Rightarrow e : [c_1 \sim c_2] \Rightarrow & \Gamma, c_1 \sim c_2 \vdash e : \tau }$$ +\subsection{Pattern Typing} + +$$\infer{\Gamma \vdash \_ \leadsto \Gamma; \tau}{} +\quad \infer{\Gamma \vdash x \leadsto \Gamma, x : \tau; \tau}{} +\quad \infer{\Gamma \vdash \ell \leadsto \Gamma; T(\ell)}{}$$ + +$$\infer{\Gamma \vdash X \leadsto \Gamma; \overline{[x_i \mapsto \tau'_i]}\tau}{ + X : \overline{x ::: \mt{Type}} \to \tau \in \Gamma + & \textrm{$\tau$ not a function type} +} +\quad \infer{\Gamma \vdash X \; p \leadsto \Gamma'; \overline{[x_i \mapsto \tau'_i]}\tau}{ + X : \overline{x ::: \mt{Type}} \to \tau'' \to \tau \in \Gamma + & \Gamma \vdash p \leadsto \Gamma'; \overline{[x_i \mapsto \tau'_i]}\tau'' +}$$ + +$$\infer{\Gamma \vdash M.X \leadsto \Gamma; \overline{[x_i \mapsto \tau'_i]}\tau}{ + \Gamma \vdash M : S + & \mt{proj}(M, S, \mt{val} \; X) = \overline{x ::: \mt{Type}} \to \tau + & \textrm{$\tau$ not a function type} +}$$ + +$$\infer{\Gamma \vdash M.X \; p \leadsto \Gamma'; \overline{[x_i \mapsto \tau'_i]}\tau}{ + \Gamma \vdash M : S + & \mt{proj}(M, S, \mt{val} \; X) = \overline{x ::: \mt{Type}} \to \tau'' \to \tau + & \Gamma \vdash p \leadsto \Gamma'; \overline{[x_i \mapsto \tau'_i]}\tau'' +}$$ + +$$\infer{\Gamma \vdash \{\overline{x = p}\} \leadsto \Gamma_n; \{\overline{x = \tau}\}}{ + \Gamma_0 = \Gamma + & \forall i: \Gamma_i \vdash p_i \leadsto \Gamma_{i+1}; \tau_i +} +\quad \infer{\Gamma \vdash \{\overline{x = p}, \ldots\} \leadsto \Gamma_n; \$([\overline{x = \tau}] \rc c)}{ + \Gamma_0 = \Gamma + & \forall i: \Gamma_i \vdash p_i \leadsto \Gamma_{i+1}; \tau_i +}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From e2c7097ddf12808ae9f108e911e93ab99e640d80 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 29 Nov 2008 11:33:51 -0500 Subject: Declaration typing --- doc/manual.tex | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 2 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index db679405..4df95230 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -181,8 +181,8 @@ $$\begin{array}{rrcll} &&& \mt{open} \; \mt{constraints} \; M & \textrm{inclusion of just the constraints from a module} \\ &&& \mt{table} \; x : c & \textrm{SQL table} \\ &&& \mt{sequence} \; x & \textrm{SQL sequence} \\ - &&& \mt{class} \; x = c & \textrm{concrete type class} \\ &&& \mt{cookie} \; x : \tau & \textrm{HTTP cookie} \\ + &&& \mt{class} \; x = c & \textrm{concrete type class} \\ \\ \textrm{Modules} & M &::=& \mt{struct} \; d^* \; \mt{end} & \textrm{constant} \\ &&& X & \textrm{variable} \\ @@ -245,8 +245,10 @@ Since there is significant mutual recursion among the judgments, we introduce th \item $\Gamma \vdash e : \tau$ is a standard typing judgment. \item $\Gamma \vdash p \leadsto \Gamma; \tau$ combines typing of patterns with calculation of which new variables they bind. \item $\Gamma \vdash d \leadsto \Gamma$ expresses how a declaration modifies a context. We overload this judgment to apply to sequences of declarations. +\item $\Gamma \vdash S$ is the signature validity judgment. +\item $\Gamma \vdash S \leq S$ is the signature compatibility judgment. \item $\Gamma \vdash M : S$ is the module signature checking judgment. -\item $\mt{proj}(M, S, V)$ is a partial function for projecting a signature item from a signature $S$, given the module $M$ that we project from. $V$ may be $\mt{con} \; x$, $\mt{val} \; x$, $\mt{signature} \; X$, or $\mt{structure} \; X$. The parameter $M$ is needed because the projected signature item may refer to other items of $S$. +\item $\mt{proj}(M, S, V)$ is a partial function for projecting a signature item from a signature $S$, given the module $M$ that we project from. $V$ may be $\mt{con} \; x$, $\mt{datatype} \; x$, $\mt{val} \; x$, $\mt{signature} \; X$, or $\mt{structure} \; X$. The parameter $M$ is needed because the projected signature item may refer to other items of $S$. \end{itemize} \subsection{Kinding} @@ -521,4 +523,81 @@ $$\infer{\Gamma \vdash \{\overline{x = p}\} \leadsto \Gamma_n; \{\overline{x = \ & \forall i: \Gamma_i \vdash p_i \leadsto \Gamma_{i+1}; \tau_i }$$ +\subsection{Declaration Typing} + +We use an auxiliary judgment $\overline{y}; x; \Gamma \vdash \overline{dc} \leadsto \Gamma'$, expressing the enrichment of $\Gamma$ with the types of the datatype constructors $\overline{dc}$, when they are known to belong to datatype $x$ with type parameters $\overline{y}$. + +This is the first judgment where we deal with type classes, for the $\mt{class}$ declaration form. We will omit their special handling in this formal specification. In the compiler, a set of available type classes and their instances is maintained, and these instances are used to fill in expression wildcards. + +We presuppose the existence of a function $\mathcal O$, where $\mathcal(M, S)$ implements the $\mt{open}$ declaration by producing a context with the appropriate entry for each available component of module $M$ with signature $S$. Where possible, $\mathcal O$ uses ``transparent'' entries (e.g., an abstract type $M.x$ is mapped to $x :: \mt{Type} = M.x$), so that the relationship with $M$ is maintained. A related function $\mathcal O_c$ builds a context containing the disjointness constraints found in $S$. + +$$\infer{\Gamma \vdash \cdot \leadsto \Gamma}{} +\quad \infer{\Gamma \vdash d, \overline{d} \leadsto \Gamma''}{ + \Gamma \vdash d \leadsto \Gamma' + & \Gamma' \vdash \overline{d} \leadsto \Gamma'' +}$$ + +$$\infer{\Gamma \vdash \mt{con} \; x :: \kappa = c \leadsto \Gamma, x :: \kappa = c}{ + \Gamma \vdash c :: \kappa +} +\quad \infer{\Gamma \vdash \mt{datatype} \; x \; \overline{y} = \overline{dc} \leadsto \Gamma'}{ + \overline{y}; x; \Gamma, x :: \mt{Type}^{\mt{len}(\overline y)} \to \mt{Type} \vdash \overline{dc} \leadsto \Gamma' +}$$ + +$$\infer{\Gamma \vdash \mt{datatype} \; x = \mt{datatype} \; M.z \leadsto \Gamma'}{ + \Gamma \vdash M : S + & \mt{proj}(M, S, \mt{datatype} \; z) = (\overline{y}, \overline{dc}) + & \overline{y}; x; \Gamma, x :: \mt{Type}^{\mt{len}(\overline y)} \to \mt{Type} = M.z \vdash \overline{dc} \leadsto \Gamma' +}$$ + +$$\infer{\Gamma \vdash \mt{val} \; x : \tau = e \leadsto \Gamma, x : \tau}{ + \Gamma \vdash e : \tau +}$$ + +$$\infer{\Gamma \vdash \mt{val} \; \mt{rec} \; \overline{x : \tau = e} \leadsto \Gamma, \overline{x : \tau}}{ + \forall i: \Gamma, \overline{x : \tau} \vdash e_i : \tau_i + & \textrm{$e_i$ starts with an expression $\lambda$, optionally preceded by constructor and disjointness $\lambda$s} +}$$ + +$$\infer{\Gamma \vdash \mt{structure} \; X : S = M \leadsto \Gamma, X : S}{ + \Gamma \vdash M : S +} +\quad \infer{\Gamma \vdash \mt{siganture} \; X = S \leadsto \Gamma, X = S}{ + \Gamma \vdash S +}$$ + +$$\infer{\Gamma \vdash \mt{open} \; M \leadsto \Gamma, \mathcal O(M, S)}{ + \Gamma \vdash M : S +}$$ + +$$\infer{\Gamma \vdash \mt{constraint} \; c_1 \sim c_2 \leadsto \Gamma}{ + \Gamma \vdash c_1 :: \{\kappa\} + & \Gamma \vdash c_2 :: \{\kappa\} + & \Gamma \vdash c_1 \sim c_2 +} +\quad \infer{\Gamma \vdash \mt{open} \; \mt{constraints} \; M \leadsto \Gamma, \mathcal O_c(M, S)}{ + \Gamma \vdash M : S +}$$ + +$$\infer{\Gamma \vdash \mt{table} \; x : c \leadsto \Gamma, x : \mt{Basis}.\mt{sql\_table} \; c}{ + \Gamma \vdash c :: \{\mt{Type}\} +} +\quad \infer{\Gamma \vdash \mt{sequence} \; x \leadsto \Gamma, x : \mt{Basis}.\mt{sql\_sequence}}{}$$ + +$$\infer{\Gamma \vdash \mt{cookie} \; x : \tau \leadsto \Gamma, x : \mt{Basis}.\mt{http\_cookie} \; \tau}{ + \Gamma \vdash \tau :: \mt{Type} +}$$ + +$$\infer{\Gamma \vdash \mt{class} \; x = c \leadsto \Gamma, x :: \mt{Type} \to \mt{Type} = c}{ + \Gamma \vdash c :: \mt{Type} \to \mt{Type} +}$$ + +$$\infer{\overline{y}; x; \Gamma \vdash \cdot \leadsto \Gamma}{} +\quad \infer{\overline{y}; x; \Gamma \vdash X \mid \overline{dc} \leadsto \Gamma', X : \overline{y ::: \mt{Type}} \to x \; \overline{y}}{ + \overline{y}; x; \Gamma \vdash \overline{dc} \leadsto \Gamma' +} +\quad \infer{\overline{y}; x; \Gamma \vdash X \; \mt{of} \; \tau \mid \overline{dc} \leadsto \Gamma', X : \overline{y ::: \mt{Type}} \to \tau \to x \; \overline{y}}{ + \overline{y}; x; \Gamma \vdash \overline{dc} \leadsto \Gamma' +}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From 022c9806c7c5d74195c0bc654c4f064384cb1d42 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 29 Nov 2008 12:58:58 -0500 Subject: Signature compatibility --- doc/manual.tex | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 4df95230..2c8379d5 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -245,8 +245,7 @@ Since there is significant mutual recursion among the judgments, we introduce th \item $\Gamma \vdash e : \tau$ is a standard typing judgment. \item $\Gamma \vdash p \leadsto \Gamma; \tau$ combines typing of patterns with calculation of which new variables they bind. \item $\Gamma \vdash d \leadsto \Gamma$ expresses how a declaration modifies a context. We overload this judgment to apply to sequences of declarations. -\item $\Gamma \vdash S$ is the signature validity judgment. -\item $\Gamma \vdash S \leq S$ is the signature compatibility judgment. +\item $\Gamma \vdash S \leq S$ is the signature compatibility judgment. We write $\Gamma \vdash S$ as shorthand for $\Gamma \vdash S \leq S$. \item $\Gamma \vdash M : S$ is the module signature checking judgment. \item $\mt{proj}(M, S, V)$ is a partial function for projecting a signature item from a signature $S$, given the module $M$ that we project from. $V$ may be $\mt{con} \; x$, $\mt{datatype} \; x$, $\mt{val} \; x$, $\mt{signature} \; X$, or $\mt{structure} \; X$. The parameter $M$ is needed because the projected signature item may refer to other items of $S$. \end{itemize} @@ -600,4 +599,37 @@ $$\infer{\overline{y}; x; \Gamma \vdash \cdot \leadsto \Gamma}{} \overline{y}; x; \Gamma \vdash \overline{dc} \leadsto \Gamma' }$$ +\subsection{Signature Compatibility} + +$$\infer{\Gamma \vdash S \equiv S}{} +\quad \infer{\Gamma \vdash S_1 \equiv S_2}{ + \Gamma \vdash S_2 \equiv S_1 +} +\quad \infer{\Gamma \vdash X \equiv S}{ + X = S \in \Gamma +} +\quad \infer{\Gamma \vdash M.X \equiv S}{ + \Gamma \vdash M : S' + & \mt{proj}(M, S', \mt{signature} \; X) = S +}$$ + +$$\infer{\Gamma \vdash S \; \mt{where} \; \mt{con} \; x = c \equiv \mt{sig} \; \overline{s^1} \; \mt{con} \; x :: \kappa = c \; \overline{s_2} \; \mt{end}}{ + \Gamma \vdash S \equiv \mt{sig} \; \overline{s^1} \; \mt{con} \; x :: \kappa \; \overline{s_2} \; \mt{end} + & \Gamma \vdash c :: \kappa +}$$ + +$$\infer{\Gamma \vdash S_1 \leq S_2}{ + \Gamma \vdash S_1 \equiv S_2 +} +\quad \infer{\Gamma \vdash \mt{sig} \; \overline{s} \; \mt{end} \leq \mt{sig} \; \mt{end}}{} +\quad \infer{\Gamma \vdash \mt{sig} \; \overline{s^1} \; s \; \overline{s^2} \; \mt{end} \leq \mt{sig} \; s' \; \overline{s} \; \mt{end}}{ + \Gamma \vdash s \leq s'; \Gamma' + & \Gamma' \vdash \mt{sig} \; \overline{s^1} \; s \; \overline{s^2} \; \mt{end} \leq \mt{sig} \; \overline{s} \; \mt{end} +}$$ + +$$\infer{\Gamma \vdash \mt{functor} (X : S_1) : S_2 \leq \mt{functor} (X : S'_1) : S'_2}{ + \Gamma \vdash S'_1 \leq S_1 + & \Gamma, X : S'_1 \vdash S_2 \leq S'_2 +}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From 509cd9c3d6cb02ff1d23a831979208e327668432 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 29 Nov 2008 13:50:53 -0500 Subject: Signature compatibility --- doc/manual.tex | 191 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 162 insertions(+), 29 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 2c8379d5..ed41acaa 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -244,10 +244,11 @@ Since there is significant mutual recursion among the judgments, we introduce th \item $\Gamma \vdash c \equiv c$ proves the computational equivalence of two constructors. This is often called a \emph{definitional equality} in the world of type theory. \item $\Gamma \vdash e : \tau$ is a standard typing judgment. \item $\Gamma \vdash p \leadsto \Gamma; \tau$ combines typing of patterns with calculation of which new variables they bind. -\item $\Gamma \vdash d \leadsto \Gamma$ expresses how a declaration modifies a context. We overload this judgment to apply to sequences of declarations. +\item $\Gamma \vdash d \leadsto \Gamma$ expresses how a declaration modifies a context. We overload this judgment to apply to sequences of declarations, as well as to signature items and sequences of signature items. +\item $\Gamma \vdash S \equiv S$ is the signature equivalence judgment. \item $\Gamma \vdash S \leq S$ is the signature compatibility judgment. We write $\Gamma \vdash S$ as shorthand for $\Gamma \vdash S \leq S$. \item $\Gamma \vdash M : S$ is the module signature checking judgment. -\item $\mt{proj}(M, S, V)$ is a partial function for projecting a signature item from a signature $S$, given the module $M$ that we project from. $V$ may be $\mt{con} \; x$, $\mt{datatype} \; x$, $\mt{val} \; x$, $\mt{signature} \; X$, or $\mt{structure} \; X$. The parameter $M$ is needed because the projected signature item may refer to other items of $S$. +\item $\mt{proj}(M, \overline{s}, V)$ is a partial function for projecting a signature item from $\overline{s}$, given the module $M$ that we project from. $V$ may be $\mt{con} \; x$, $\mt{datatype} \; x$, $\mt{val} \; x$, $\mt{signature} \; X$, or $\mt{structure} \; X$. The parameter $M$ is needed because the projected signature item may refer to other items from $\overline{s}$. \end{itemize} \subsection{Kinding} @@ -263,12 +264,12 @@ $$\infer{\Gamma \vdash (c) :: \kappa :: \kappa}{ }$$ $$\infer{\Gamma \vdash M.x :: \kappa}{ - \Gamma \vdash M : S - & \mt{proj}(M, S, \mt{con} \; x) = \kappa + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{con} \; x) = \kappa } \quad \infer{\Gamma \vdash M.x :: \kappa}{ - \Gamma \vdash M : S - & \mt{proj}(M, S, \mt{con} \; x) = (\kappa, c) + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{con} \; x) = (\kappa, c) }$$ $$\infer{\Gamma \vdash \tau_1 \to \tau_2 :: \mt{Type}}{ @@ -374,8 +375,8 @@ $$\infer{\Gamma \vdash x \equiv c}{ x :: \kappa = c \in \Gamma } \quad \infer{\Gamma \vdash M.x \equiv c}{ - \Gamma \vdash M : S - & \mt{proj}(M, S, \mt{con} \; x) = (\kappa, c) + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{con} \; x) = (\kappa, c) } \quad \infer{\Gamma \vdash (\overline c).i \equiv c_i}{}$$ @@ -417,15 +418,15 @@ $$\infer{\Gamma \vdash x : \mathcal I(\tau)}{ x : \tau \in \Gamma } \quad \infer{\Gamma \vdash M.x : \mathcal I(\tau)}{ - \Gamma \vdash M : S - & \mt{proj}(M, S, \mt{val} \; x) = \tau + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{val} \; x) = \tau } \quad \infer{\Gamma \vdash X : \mathcal I(\tau)}{ X : \tau \in \Gamma } \quad \infer{\Gamma \vdash M.X : \mathcal I(\tau)}{ - \Gamma \vdash M : S - & \mt{proj}(M, S, \mt{val} \; X) = \tau + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{val} \; X) = \tau }$$ $$\infer{\Gamma \vdash e_1 \; e_2 : \tau_2}{ @@ -502,14 +503,14 @@ $$\infer{\Gamma \vdash X \leadsto \Gamma; \overline{[x_i \mapsto \tau'_i]}\tau}{ }$$ $$\infer{\Gamma \vdash M.X \leadsto \Gamma; \overline{[x_i \mapsto \tau'_i]}\tau}{ - \Gamma \vdash M : S - & \mt{proj}(M, S, \mt{val} \; X) = \overline{x ::: \mt{Type}} \to \tau + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{val} \; X) = \overline{x ::: \mt{Type}} \to \tau & \textrm{$\tau$ not a function type} }$$ $$\infer{\Gamma \vdash M.X \; p \leadsto \Gamma'; \overline{[x_i \mapsto \tau'_i]}\tau}{ - \Gamma \vdash M : S - & \mt{proj}(M, S, \mt{val} \; X) = \overline{x ::: \mt{Type}} \to \tau'' \to \tau + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{val} \; X) = \overline{x ::: \mt{Type}} \to \tau'' \to \tau & \Gamma \vdash p \leadsto \Gamma'; \overline{[x_i \mapsto \tau'_i]}\tau'' }$$ @@ -528,7 +529,9 @@ We use an auxiliary judgment $\overline{y}; x; \Gamma \vdash \overline{dc} \lead This is the first judgment where we deal with type classes, for the $\mt{class}$ declaration form. We will omit their special handling in this formal specification. In the compiler, a set of available type classes and their instances is maintained, and these instances are used to fill in expression wildcards. -We presuppose the existence of a function $\mathcal O$, where $\mathcal(M, S)$ implements the $\mt{open}$ declaration by producing a context with the appropriate entry for each available component of module $M$ with signature $S$. Where possible, $\mathcal O$ uses ``transparent'' entries (e.g., an abstract type $M.x$ is mapped to $x :: \mt{Type} = M.x$), so that the relationship with $M$ is maintained. A related function $\mathcal O_c$ builds a context containing the disjointness constraints found in $S$. +We presuppose the existence of a function $\mathcal O$, where $\mathcal(M, \overline{s})$ implements the $\mt{open}$ declaration by producing a context with the appropriate entry for each available component of module $M$ with signature items $\overline{s}$. Where possible, $\mathcal O$ uses ``transparent'' entries (e.g., an abstract type $M.x$ is mapped to $x :: \mt{Type} = M.x$), so that the relationship with $M$ is maintained. A related function $\mathcal O_c$ builds a context containing the disjointness constraints found in $S$. + +We write $\kappa_1^n \to \kappa$ as a shorthand, where $\kappa_1^0 \to \kappa = \kappa$ and $\kappa_1^{n+1} \to \kappa_2 = \kappa_1 \to (\kappa_1^n \to \kappa_2)$. We write $\mt{len}(\overline{y})$ for the length of vector $\overline{y}$ of variables. $$\infer{\Gamma \vdash \cdot \leadsto \Gamma}{} \quad \infer{\Gamma \vdash d, \overline{d} \leadsto \Gamma''}{ @@ -544,8 +547,8 @@ $$\infer{\Gamma \vdash \mt{con} \; x :: \kappa = c \leadsto \Gamma, x :: \kappa }$$ $$\infer{\Gamma \vdash \mt{datatype} \; x = \mt{datatype} \; M.z \leadsto \Gamma'}{ - \Gamma \vdash M : S - & \mt{proj}(M, S, \mt{datatype} \; z) = (\overline{y}, \overline{dc}) + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{datatype} \; z) = (\overline{y}, \overline{dc}) & \overline{y}; x; \Gamma, x :: \mt{Type}^{\mt{len}(\overline y)} \to \mt{Type} = M.z \vdash \overline{dc} \leadsto \Gamma' }$$ @@ -561,12 +564,12 @@ $$\infer{\Gamma \vdash \mt{val} \; \mt{rec} \; \overline{x : \tau = e} \leadsto $$\infer{\Gamma \vdash \mt{structure} \; X : S = M \leadsto \Gamma, X : S}{ \Gamma \vdash M : S } -\quad \infer{\Gamma \vdash \mt{siganture} \; X = S \leadsto \Gamma, X = S}{ +\quad \infer{\Gamma \vdash \mt{signature} \; X = S \leadsto \Gamma, X = S}{ \Gamma \vdash S }$$ -$$\infer{\Gamma \vdash \mt{open} \; M \leadsto \Gamma, \mathcal O(M, S)}{ - \Gamma \vdash M : S +$$\infer{\Gamma \vdash \mt{open} \; M \leadsto \Gamma, \mathcal O(M, \overline{s})}{ + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} }$$ $$\infer{\Gamma \vdash \mt{constraint} \; c_1 \sim c_2 \leadsto \Gamma}{ @@ -574,8 +577,8 @@ $$\infer{\Gamma \vdash \mt{constraint} \; c_1 \sim c_2 \leadsto \Gamma}{ & \Gamma \vdash c_2 :: \{\kappa\} & \Gamma \vdash c_1 \sim c_2 } -\quad \infer{\Gamma \vdash \mt{open} \; \mt{constraints} \; M \leadsto \Gamma, \mathcal O_c(M, S)}{ - \Gamma \vdash M : S +\quad \infer{\Gamma \vdash \mt{open} \; \mt{constraints} \; M \leadsto \Gamma, \mathcal O_c(M, \overline{s})}{ + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} }$$ $$\infer{\Gamma \vdash \mt{table} \; x : c \leadsto \Gamma, x : \mt{Basis}.\mt{sql\_table} \; c}{ @@ -599,8 +602,62 @@ $$\infer{\overline{y}; x; \Gamma \vdash \cdot \leadsto \Gamma}{} \overline{y}; x; \Gamma \vdash \overline{dc} \leadsto \Gamma' }$$ +\subsection{Signature Item Typing} + +We appeal to a signature item analogue of the $\mathcal O$ function from the last subsection. + +$$\infer{\Gamma \vdash \cdot \leadsto \Gamma}{} +\quad \infer{\Gamma \vdash s, \overline{s} \leadsto \Gamma''}{ + \Gamma \vdash s \leadsto \Gamma' + & \Gamma' \vdash \overline{s} \leadsto \Gamma'' +}$$ + +$$\infer{\Gamma \vdash \mt{con} \; x :: \kappa \leadsto \Gamma, x :: \kappa}{} +\quad \infer{\Gamma \vdash \mt{con} \; x :: \kappa = c \leadsto \Gamma, x :: \kappa = c}{ + \Gamma \vdash c :: \kappa +} +\quad \infer{\Gamma \vdash \mt{datatype} \; x \; \overline{y} = \overline{dc} \leadsto \Gamma'}{ + \overline{y}; x; \Gamma, x :: \mt{Type}^{\mt{len}(\overline y)} \to \mt{Type} \vdash \overline{dc} \leadsto \Gamma' +}$$ + +$$\infer{\Gamma \vdash \mt{datatype} \; x = \mt{datatype} \; M.z \leadsto \Gamma'}{ + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{datatype} \; z) = (\overline{y}, \overline{dc}) + & \overline{y}; x; \Gamma, x :: \mt{Type}^{\mt{len}(\overline y)} \to \mt{Type} = M.z \vdash \overline{dc} \leadsto \Gamma' +}$$ + +$$\infer{\Gamma \vdash \mt{val} \; x : \tau \leadsto \Gamma, x : \tau}{ + \Gamma \vdash \tau :: \mt{Type} +}$$ + +$$\infer{\Gamma \vdash \mt{structure} \; X : S \leadsto \Gamma, X : S}{ + \Gamma \vdash S +} +\quad \infer{\Gamma \vdash \mt{signature} \; X = S \leadsto \Gamma, X = S}{ + \Gamma \vdash S +}$$ + +$$\infer{\Gamma \vdash \mt{include} \; S \leadsto \Gamma, \mathcal O(\overline{s})}{ + \Gamma \vdash S + & \Gamma \vdash S \equiv \mt{sig} \; \overline{s} \; \mt{end} +}$$ + +$$\infer{\Gamma \vdash \mt{constraint} \; c_1 \sim c_2 \leadsto \Gamma, c_1 \sim c_2}{ + \Gamma \vdash c_1 :: \{\kappa\} + & \Gamma \vdash c_2 :: \{\kappa\} +}$$ + +$$\infer{\Gamma \vdash \mt{class} \; x = c \leadsto \Gamma, x :: \mt{Type} \to \mt{Type} = c}{ + \Gamma \vdash c :: \mt{Type} \to \mt{Type} +} +\quad \infer{\Gamma \vdash \mt{class} \; x \leadsto \Gamma, x :: \mt{Type} \to \mt{Type}}{}$$ + \subsection{Signature Compatibility} +To simplify the judgments in this section, we assume that all signatures are alpha-varied as necessary to avoid including mmultiple bindings for the same identifier. This is in addition to the usual alpha-variation of locally-bound variables. + +We rely on a judgment $\Gamma \vdash \overline{s} \leq s'$, which expresses the occurrence in signature items $\overline{s}$ of an item compatible with $s'$. We also use a judgment $\Gamma \vdash \overline{dc} \leq \overline{dc}$, which expresses compatibility of datatype definitions. + $$\infer{\Gamma \vdash S \equiv S}{} \quad \infer{\Gamma \vdash S_1 \equiv S_2}{ \Gamma \vdash S_2 \equiv S_1 @@ -609,22 +666,34 @@ $$\infer{\Gamma \vdash S \equiv S}{} X = S \in \Gamma } \quad \infer{\Gamma \vdash M.X \equiv S}{ - \Gamma \vdash M : S' - & \mt{proj}(M, S', \mt{signature} \; X) = S + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{signature} \; X) = S }$$ $$\infer{\Gamma \vdash S \; \mt{where} \; \mt{con} \; x = c \equiv \mt{sig} \; \overline{s^1} \; \mt{con} \; x :: \kappa = c \; \overline{s_2} \; \mt{end}}{ \Gamma \vdash S \equiv \mt{sig} \; \overline{s^1} \; \mt{con} \; x :: \kappa \; \overline{s_2} \; \mt{end} & \Gamma \vdash c :: \kappa +} +\quad \infer{\Gamma \vdash \mt{sig} \; \overline{s^1} \; \mt{include} \; S \; \overline{s^2} \; \mt{end} \equiv \mt{sig} \; \overline{s^1} \; \overline{s} \; \overline{s^2} \; \mt{end}}{ + \Gamma \vdash S \equiv \mt{sig} \; \overline{s} \; \mt{end} }$$ $$\infer{\Gamma \vdash S_1 \leq S_2}{ \Gamma \vdash S_1 \equiv S_2 } \quad \infer{\Gamma \vdash \mt{sig} \; \overline{s} \; \mt{end} \leq \mt{sig} \; \mt{end}}{} -\quad \infer{\Gamma \vdash \mt{sig} \; \overline{s^1} \; s \; \overline{s^2} \; \mt{end} \leq \mt{sig} \; s' \; \overline{s} \; \mt{end}}{ - \Gamma \vdash s \leq s'; \Gamma' - & \Gamma' \vdash \mt{sig} \; \overline{s^1} \; s \; \overline{s^2} \; \mt{end} \leq \mt{sig} \; \overline{s} \; \mt{end} +\quad \infer{\Gamma \vdash \mt{sig} \; \overline{s} \; \mt{end} \leq \mt{sig} \; s' \; \overline{s'} \; \mt{end}}{ + \Gamma \vdash \overline{s} \leq s' + & \Gamma \vdash s' \leadsto \Gamma' + & \Gamma' \vdash \mt{sig} \; \overline{s} \; \mt{end} \leq \mt{sig} \; \overline{s'} \; \mt{end} +}$$ + +$$\infer{\Gamma \vdash s \; \overline{s} \leq s'}{ + \Gamma \vdash s \leq s' +} +\quad \infer{\Gamma \vdash s \; \overline{s} \leq s'}{ + \Gamma \vdash s \leadsto \Gamma' + & \Gamma' \vdash \overline{s} \leq s' }$$ $$\infer{\Gamma \vdash \mt{functor} (X : S_1) : S_2 \leq \mt{functor} (X : S'_1) : S'_2}{ @@ -632,4 +701,68 @@ $$\infer{\Gamma \vdash \mt{functor} (X : S_1) : S_2 \leq \mt{functor} (X : S'_1) & \Gamma, X : S'_1 \vdash S_2 \leq S'_2 }$$ +$$\infer{\Gamma \vdash \mt{con} \; x :: \kappa \leq \mt{con} \; x :: \kappa}{} +\quad \infer{\Gamma \vdash \mt{con} \; x :: \kappa = c \leq \mt{con} \; x :: \kappa}{} +\quad \infer{\Gamma \vdash \mt{datatype} \; x \; \overline{y} = \overline{dc} \leq \mt{con} \; x :: \mt{Type}}{}$$ + +$$\infer{\Gamma \vdash \mt{datatype} \; x = \mt{datatype} \; M.z \leq \mt{con} \; x :: \mt{Type}^{\mt{len}(y)} \to \mt{Type}}{ + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{datatype} \; z) = (\overline{y}, \overline{dc}) +}$$ + +$$\infer{\Gamma \vdash \mt{class} \; x \leq \mt{con} \; x :: \mt{Type} \to \mt{Type}}{} +\quad \infer{\Gamma \vdash \mt{class} \; x = c \leq \mt{con} \; x :: \mt{Type} \to \mt{Type}}{}$$ + +$$\infer{\Gamma \vdash \mt{con} \; x :: \kappa = c_1 \leq \mt{con} \; x :: \mt{\kappa} = c_2}{ + \Gamma \vdash c_1 \equiv c_2 +} +\quad \infer{\Gamma \vdash \mt{class} \; x = c_1 \leq \mt{con} \; x :: \mt{Type} \to \mt{Type} = c_2}{ + \Gamma \vdash c_1 \equiv c_2 +}$$ + +$$\infer{\Gamma \vdash \mt{datatype} \; x \; \overline{y} = \overline{dc} \leq \mt{datatype} \; x \; \overline{y} = \overline{dc'}}{ + \Gamma, \overline{y :: \mt{Type}} \vdash \overline{dc} \leq \overline{dc'} +}$$ + +$$\infer{\Gamma \vdash \mt{datatype} \; x = \mt{datatype} \; M.z \leq \mt{datatype} \; x \; \overline{y} = \overline{dc'}}{ + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{datatype} \; z) = (\overline{y}, \overline{dc}) + & \Gamma, \overline{y :: \mt{Type}} \vdash \overline{dc} \leq \overline{dc'} +}$$ + +$$\infer{\Gamma \vdash \cdot \leq \cdot}{} +\quad \infer{\Gamma \vdash X; \overline{dc} \leq X; \overline{dc'}}{ + \Gamma \vdash \overline{dc} \leq \overline{dc'} +} +\quad \infer{\Gamma \vdash X \; \mt{of} \; \tau_1; \overline{dc} \leq X \; \mt{of} \; \tau_2; \overline{dc'}}{ + \Gamma \vdash \tau_1 \equiv \tau_2 + & \Gamma \vdash \overline{dc} \leq \overline{dc'} +}$$ + +$$\infer{\Gamma \vdash \mt{datatype} \; x = \mt{datatype} \; M.z \leq \mt{datatype} \; x = \mt{datatype} \; M'.z'}{ + \Gamma \vdash M.z \equiv M'.z' +}$$ + +$$\infer{\Gamma \vdash \mt{val} \; x : \tau_1 \leq \mt{val} \; x : \tau_2}{ + \Gamma \vdash \tau_1 \equiv \tau_2 +} +\quad \infer{\Gamma \vdash \mt{structure} \; X : S_1 \leq \mt{structure} \; X : S_2}{ + \Gamma \vdash S_1 \leq S_2 +} +\quad \infer{\Gamma \vdash \mt{signature} \; X = S_1 \leq \mt{signature} \; X = S_2}{ + \Gamma \vdash S_1 \leq S_2 + & \Gamma \vdash S_2 \leq S_1 +}$$ + +$$\infer{\Gamma \vdash \mt{constraint} \; c_1 \sim c_2 \leq \mt{constraint} \; c'_1 \sim c'_2}{ + \Gamma \vdash c_1 \equiv c'_1 + & \Gamma \vdash c_2 \equiv c'_2 +}$$ + +$$\infer{\Gamma \vdash \mt{class} \; x \leq \mt{class} \; x}{} +\quad \infer{\Gamma \vdash \mt{class} \; x = c \leq \mt{class} \; x}{} +\quad \infer{\Gamma \vdash \mt{class} \; x = c_1 \leq \mt{class} \; x = c_2}{ + \Gamma \vdash c_1 \equiv c_2 +}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From 2cf99ae8367d64360d18f7e838f905419f4c80ef Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 29 Nov 2008 14:09:43 -0500 Subject: Module typing --- doc/manual.tex | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index ed41acaa..53a2b787 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -765,4 +765,54 @@ $$\infer{\Gamma \vdash \mt{class} \; x \leq \mt{class} \; x}{} \Gamma \vdash c_1 \equiv c_2 }$$ +\subsection{Module Typing} + +We use a helper function $\mt{sigOf}$, which converts declarations and sequences of declarations into their principal signature items and sequences of signature items, respectively. + +$$\infer{\Gamma \vdash M : S}{ + \Gamma \vdash M : S' + & \Gamma \vdash S' \leq S +} +\quad \infer{\Gamma \vdash \mt{struct} \; \overline{d} \; \mt{end} : \mt{sig} \; \mt{sigOf}(\overline{d}) \; \mt{end}}{ + \Gamma \vdash \overline{d} \leadsto \Gamma' +} +\quad \infer{\Gamma \vdash X : S}{ + X : S \in \Gamma +}$$ + +$$\infer{\Gamma \vdash M.X : S}{ + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} + & \mt{proj}(M, \overline{s}, \mt{structure} \; X) = S +}$$ + +$$\infer{\Gamma \vdash M_1(M_2) : [X \mapsto M_2]S_2}{ + \Gamma \vdash M_1 : \mt{functor}(X : S_1) : S_2 + & \Gamma \vdash M_2 : S_1 +} +\quad \infer{\Gamma \vdash \mt{functor} (X : S_1) : S_2 = M : \mt{functor} (X : S_1) : S_2}{ + \Gamma \vdash S_1 + & \Gamma, X : S_1 \vdash S_2 + & \Gamma, X : S_1 \vdash M : S_2 +}$$ + +\begin{eqnarray*} + \mt{sigOf}(\cdot) &=& \cdot \\ + \mt{sigOf}(s \; \overline{s'}) &=& \mt{sigOf}(s) \; \mt{sigOf}(\overline{s'}) \\ + \\ + \mt{sigOf}(\mt{con} \; x :: \kappa = c) &=& \mt{con} \; x :: \kappa = c \\ + \mt{sigOf}(\mt{datatype} \; x \; \overline{y} = \overline{dc}) &=& \mt{datatype} \; x \; \overline{y} = \overline{dc} \\ + \mt{sigOf}(\mt{datatype} \; x = \mt{datatype} \; M.z) &=& \mt{datatype} \; x = \mt{datatype} \; M.z \\ + \mt{sigOf}(\mt{val} \; x : \tau = e) &=& \mt{val} \; x : \tau \\ + \mt{sigOf}(\mt{val} \; \mt{rec} \; \overline{x : \tau = e}) &=& \overline{\mt{val} \; x : \tau} \\ + \mt{sigOf}(\mt{structure} \; X : S = M) &=& \mt{structure} \; X : S \\ + \mt{sigOf}(\mt{signature} \; X = S) &=& \mt{signature} \; X = S \\ + \mt{sigOf}(\mt{open} \; M) &=& \mt{include} \; S \textrm{ (where $\Gamma \vdash M : S$)} \\ + \mt{sigOf}(\mt{constraint} \; c_1 \sim c_2) &=& \mt{constraint} \; c_1 \sim c_2 \\ + \mt{sigOf}(\mt{open} \; \mt{constraints} \; M) &=& \cdot \\ + \mt{sigOf}(\mt{table} \; x : c) &=& \mt{table} \; x : c \\ + \mt{sigOf}(\mt{sequence} \; x) &=& \mt{sequence} \; x \\ + \mt{sigOf}(\mt{cookie} \; x : \tau) &=& \mt{cookie} \; x : \tau \\ + \mt{sigOf}(\mt{class} \; x = c) &=& \mt{class} \; x = c \\ +\end{eqnarray*} + \end{document} \ No newline at end of file -- cgit v1.2.3 From bcddef561bc3d980de9cbec25605accb2334c115 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 29 Nov 2008 14:32:33 -0500 Subject: selfify --- doc/manual.tex | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 53a2b787..eac33bc6 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -249,6 +249,7 @@ Since there is significant mutual recursion among the judgments, we introduce th \item $\Gamma \vdash S \leq S$ is the signature compatibility judgment. We write $\Gamma \vdash S$ as shorthand for $\Gamma \vdash S \leq S$. \item $\Gamma \vdash M : S$ is the module signature checking judgment. \item $\mt{proj}(M, \overline{s}, V)$ is a partial function for projecting a signature item from $\overline{s}$, given the module $M$ that we project from. $V$ may be $\mt{con} \; x$, $\mt{datatype} \; x$, $\mt{val} \; x$, $\mt{signature} \; X$, or $\mt{structure} \; X$. The parameter $M$ is needed because the projected signature item may refer to other items from $\overline{s}$. +\item $\mt{selfify}(M, \overline{s})$ adds information to signature items $\overline{s}$ to reflect the fact that we are concerned with the particular module $M$. This function is overloaded to work over individual signature items as well. \end{itemize} \subsection{Kinding} @@ -563,8 +564,13 @@ $$\infer{\Gamma \vdash \mt{val} \; \mt{rec} \; \overline{x : \tau = e} \leadsto $$\infer{\Gamma \vdash \mt{structure} \; X : S = M \leadsto \Gamma, X : S}{ \Gamma \vdash M : S + & \textrm{ ($M$ not a $\mt{struct} \; \ldots \; \mt{end}$)} } -\quad \infer{\Gamma \vdash \mt{signature} \; X = S \leadsto \Gamma, X = S}{ +\quad \infer{\Gamma \vdash \mt{structure} \; X : S = \mt{struct} \; \overline{d} \; \mt{end} \leadsto \Gamma, X : \mt{selfify}(X, \overline{s})}{ + \Gamma \vdash \mt{struct} \; \overline{d} \; \mt{end} : \mt{sig} \; \overline{s} \; \mt{end} +}$$ + +$$\infer{\Gamma \vdash \mt{signature} \; X = S \leadsto \Gamma, X = S}{ \Gamma \vdash S }$$ @@ -815,4 +821,21 @@ $$\infer{\Gamma \vdash M_1(M_2) : [X \mapsto M_2]S_2}{ \mt{sigOf}(\mt{class} \; x = c) &=& \mt{class} \; x = c \\ \end{eqnarray*} +\begin{eqnarray*} + \mt{selfify}(M, \cdot) &=& \cdot \\ + \mt{selfify}(M, s \; \overline{s'}) &=& \mt{selfify}(M, \sigma, s) \; \mt{selfify}(M, \overline{s'}) \\ + \\ + \mt{selfify}(M, \mt{con} \; x :: \kappa) &=& \mt{con} \; x :: \kappa = M.x \\ + \mt{selfify}(M, \mt{con} \; x :: \kappa = c) &=& \mt{con} \; x :: \kappa = c \\ + \mt{selfify}(M, \mt{datatype} \; x \; \overline{y} = \overline{dc}) &=& \mt{datatype} \; x \; \overline{y} = \mt{datatype} \; M.x \\ + \mt{selfify}(M, \mt{datatype} \; x = \mt{datatype} \; M'.z) &=& \mt{datatype} \; x = \mt{datatype} \; M'.z \\ + \mt{selfify}(M, \mt{val} \; x : \tau) &=& \mt{val} \; x : \tau \\ + \mt{selfify}(M, \mt{structure} \; X : S) &=& \mt{structure} \; X : \mt{selfify}(M.X, \overline{s}) \textrm{ (where $\Gamma \vdash S \equiv \mt{sig} \; \overline{s} \; \mt{end}$)} \\ + \mt{selfify}(M, \mt{signature} \; X = S) &=& \mt{signature} \; X = S \\ + \mt{selfify}(M, \mt{include} \; S) &=& \mt{include} \; S \\ + \mt{selfify}(M, \mt{constraint} \; c_1 \sim c_2) &=& \mt{constraint} \; c_1 \sim c_2 \\ + \mt{selfify}(M, \mt{class} \; x) &=& \mt{class} \; x = M.x \\ + \mt{selfify}(M, \mt{class} \; x = c) &=& \mt{class} \; x = c \\ +\end{eqnarray*} + \end{document} \ No newline at end of file -- cgit v1.2.3 From bd43499d17cec3123d5462233ea487b41e77a80f Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 29 Nov 2008 15:04:57 -0500 Subject: Module projection --- .hgignore | 1 + doc/manual.tex | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) (limited to 'doc') diff --git a/.hgignore b/.hgignore index fe5b6659..4e578224 100644 --- a/.hgignore +++ b/.hgignore @@ -32,3 +32,4 @@ demo/demo.* *.dvi *.pdf *.ps +*.toc diff --git a/doc/manual.tex b/doc/manual.tex index eac33bc6..713bbe60 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -15,6 +15,8 @@ \maketitle +\tableofcontents + \section{Ur Syntax} In this section, we describe the syntax of Ur, deferring to a later section discussion of most of the syntax specific to SQL and XML. The sole exceptions are the declaration forms for tables, sequences, and cookies. @@ -838,4 +840,43 @@ $$\infer{\Gamma \vdash M_1(M_2) : [X \mapsto M_2]S_2}{ \mt{selfify}(M, \mt{class} \; x = c) &=& \mt{class} \; x = c \\ \end{eqnarray*} +\subsection{Module Projection} + +\begin{eqnarray*} + \mt{proj}(M, \mt{con} \; x :: \kappa \; \overline{s}, \mt{con} \; x) &=& \kappa \\ + \mt{proj}(M, \mt{con} \; x :: \kappa = c \; \overline{s}, \mt{con} \; x) &=& (\kappa, c) \\ + \mt{proj}(M, \mt{datatype} \; x \; \overline{y} = \overline{dc} \; \overline{s}, \mt{con} \; x) &=& \mt{Type}^{\mt{len}(\overline{y})} \to \mt{Type} \\ + \mt{proj}(M, \mt{datatype} \; x = \mt{datatype} \; M'.z \; \overline{s}, \mt{con} \; x) &=& (\mt{Type}^{\mt{len}(\overline{y})} \to \mt{Type}, M'.z) \textrm{ (where $\Gamma \vdash M' : \mt{sig} \; \overline{s'} \; \mt{end}$} \\ + && \textrm{and $\mt{proj}(M', \overline{s'}, \mt{datatype} \; z) = (\overline{y}, \overline{dc})$)} \\ + \mt{proj}(M, \mt{class} \; x \; \overline{s}, \mt{con} \; x) &=& \mt{Type} \to \mt{Type} \\ + \mt{proj}(M, \mt{class} \; x = c \; \overline{s}, \mt{con} \; x) &=& (\mt{Type} \to \mt{Type}, c) \\ + \\ + \mt{proj}(M, \mt{datatype} \; x \; \overline{y} = \overline{dc} \; \overline{s}, \mt{datatype} \; x) &=& (\overline{y}, \overline{dc}) \\ + \mt{proj}(M, \mt{datatype} \; x = \mt{datatype} \; M'.z \; \overline{s}, \mt{con} \; x) &=& \mt{proj}(M', \overline{s'}, \mt{datatype} \; z) \textrm{ (where $\Gamma \vdash M' : \mt{sig} \; \overline{s'} \; \mt{end}$)} \\ + \\ + \mt{proj}(M, \mt{val} \; x : \tau \; \overline{s}, \mt{val} \; x) &=& \tau \\ + \mt{proj}(M, \mt{datatype} \; x \; \overline{y} = \overline{dc} \; \overline{s}, \mt{val} \; X) &=& \overline{y ::: \mt{Type}} \to M.x \; \overline y \textrm{ (where $X \in \overline{dc}$)} \\ + \mt{proj}(M, \mt{datatype} \; x \; \overline{y} = \overline{dc} \; \overline{s}, \mt{val} \; X) &=& \overline{y ::: \mt{Type}} \to \tau \to M.x \; \overline y \textrm{ (where $X \; \mt{of} \; \tau \in \overline{dc}$)} \\ + \mt{proj}(M, \mt{datatype} \; x = \mt{datatype} \; M'.z, \mt{val} \; X) &=& \overline{y ::: \mt{Type}} \to M.x \; \overline y \textrm{ (where $\Gamma \vdash M' : \mt{sig} \; \overline{s'} \; \mt{end}$} \\ + && \textrm{and $\mt{proj}(M', \overline{s'}, \mt{datatype} \; z = (\overline{y}, \overline{dc})$ and $X \in \overline{dc}$)} \\ + \mt{proj}(M, \mt{datatype} \; x = \mt{datatype} \; M'.z, \mt{val} \; X) &=& \overline{y ::: \mt{Type}} \to \tau \to M.x \; \overline y \textrm{ (where $\Gamma \vdash M' : \mt{sig} \; \overline{s'} \; \mt{end}$} \\ + && \textrm{and $\mt{proj}(M', \overline{s'}, \mt{datatype} \; z = (\overline{y}, \overline{dc})$ and $X : \tau \in \overline{dc}$)} \\ + \\ + \mt{proj}(M, \mt{structure} \; X : S \; \overline{s}, \mt{structure} \; X) &=& S \\ + \\ + \mt{proj}(M, \mt{signature} \; X = S \; \overline{s}, \mt{signature} \; X) &=& S \\ + \\ + \mt{proj}(M, \mt{con} \; x :: \kappa \; \overline{s}, V) &=& [x \mapsto M.x]\mt{proj}(M, \overline{s}, V) \\ + \mt{proj}(M, \mt{con} \; x :: \kappa = c \; \overline{s}, V) &=& [x \mapsto M.x]\mt{proj}(M, \overline{s}, V) \\ + \mt{proj}(M, \mt{datatype} \; x \; \overline{y} = \overline{dc} \; \overline{s}, V) &=& [x \mapsto M.x]\mt{proj}(M, \overline{s}, V) \\ + \mt{proj}(M, \mt{datatype} \; x = \mt{datatype} \; M'.z \; \overline{s}, V) &=& [x \mapsto M.x]\mt{proj}(M, \overline{s}, V) \\ + \mt{proj}(M, \mt{val} \; x : \tau \; \overline{s}, V) &=& \mt{proj}(M, \overline{s}, V) \\ + \mt{proj}(M, \mt{structure} \; X : S \; \overline{s}, V) &=& [X \mapsto M.X]\mt{proj}(M, \overline{s}, V) \\ + \mt{proj}(M, \mt{signature} \; X = S \; \overline{s}, V) &=& [X \mapsto M.X]\mt{proj}(M, \overline{s}, V) \\ + \mt{proj}(M, \mt{include} \; S \; \overline{s}, V) &=& \mt{proj}(M, \overline{s'} \; \overline{s}, V) \textrm{ (where $\Gamma \vdash S \equiv \mt{sig} \; \overline{s'} \; \mt{end}$)} \\ + \mt{proj}(M, \mt{constraint} \; c_1 \sim c_2 \; \overline{s}, V) &=& \mt{proj}(M, \overline{s}, V) \\ + \mt{proj}(M, \mt{class} \; x \; \overline{s}, V) &=& [x \mapsto M.x]\mt{proj}(M, \overline{s}, V) \\ + \mt{proj}(M, \mt{class} \; x = c \; \overline{s}, V) &=& [x \mapsto M.x]\mt{proj}(M, \overline{s}, V) \\ +\end{eqnarray*} + \end{document} \ No newline at end of file -- cgit v1.2.3 From 154fd594ccb664b07b27d63bd9ffee41801dd4d1 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 6 Dec 2008 12:01:12 -0500 Subject: Type inference --- doc/manual.tex | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 713bbe60..8ef6a889 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -358,7 +358,7 @@ $$\infer{\Gamma \vdash c \hookrightarrow \{c\}}{} \Gamma \vdash c \hookrightarrow C }$$ -\subsection{Definitional Equality} +\subsection{\label{definitional}Definitional Equality} We use $\mathcal C$ to stand for a one-hole context that, when filled, yields a constructor. The notation $\mathcal C[c]$ plugs $c$ into $\mathcal C$. We omit the standard definition of one-hole contexts. We write $[x \mapsto c_1]c_2$ for capture-avoiding substitution of $c_1$ for $x$ in $c_2$. @@ -530,7 +530,7 @@ $$\infer{\Gamma \vdash \{\overline{x = p}\} \leadsto \Gamma_n; \{\overline{x = \ We use an auxiliary judgment $\overline{y}; x; \Gamma \vdash \overline{dc} \leadsto \Gamma'$, expressing the enrichment of $\Gamma$ with the types of the datatype constructors $\overline{dc}$, when they are known to belong to datatype $x$ with type parameters $\overline{y}$. -This is the first judgment where we deal with type classes, for the $\mt{class}$ declaration form. We will omit their special handling in this formal specification. In the compiler, a set of available type classes and their instances is maintained, and these instances are used to fill in expression wildcards. +This is the first judgment where we deal with type classes, for the $\mt{class}$ declaration form. We will omit their special handling in this formal specification. Section \ref{typeclasses} gives an informal description of how type classes influence type inference. We presuppose the existence of a function $\mathcal O$, where $\mathcal(M, \overline{s})$ implements the $\mt{open}$ declaration by producing a context with the appropriate entry for each available component of module $M$ with signature items $\overline{s}$. Where possible, $\mathcal O$ uses ``transparent'' entries (e.g., an abstract type $M.x$ is mapped to $x :: \mt{Type} = M.x$), so that the relationship with $M$ is maintained. A related function $\mathcal O_c$ builds a context containing the disjointness constraints found in $S$. @@ -879,4 +879,40 @@ $$\infer{\Gamma \vdash M_1(M_2) : [X \mapsto M_2]S_2}{ \mt{proj}(M, \mt{class} \; x = c \; \overline{s}, V) &=& [x \mapsto M.x]\mt{proj}(M, \overline{s}, V) \\ \end{eqnarray*} + +\section{Type Inference} + +The Ur/Web compiler uses \emph{heuristic type inference}, with no claims of completeness with respect to the declarative specification of the last section. The rules in use seem to work well in practice. This section summarizes those rules, to help Ur programmers predict what will work and what won't. + +\subsection{Basic Unification} + +Type-checkers for languages based on the Hindly-Milner type discipline, like ML and Haskell, take advantage of \emph{principal typing} properties, making complete type inference relatively straightforward. Inference algorithms are traditionally implemented using type unification variables, at various points asserting equalities between types, in the process discovering the values of type variables. The Ur/Web compiler uses the same basic strategy, but the complexity of the type system rules out easy completeness. + +Type-checking can require evaluating recursive functional programs, thanks to the type-level $\mt{fold}$ operator. When a unification variable appears in such a type, the next step of computation can be undetermined. The value of that variable might be determined later, but this would be ``too late'' for the unification problems generated at the first occurrence. This is the essential source of incompletness. + +Nonetheless, the unification engine tends to do reasonably well. Unlike in ML, polymorphism is never inferred in definitions; it must be indicated explicitly by writing out constructor-level parameters. By writing these and other annotations, the programmer can generally get the type inference engine to do most of the type reconstruction work. + +\subsection{Unifying Record Types} + +The type inference engine tries to take advantage of the algebraic rules governing type-level records, as shown in Section \ref{definitional}. When two constructors of record kind are unified, they are reduce to normal forms, with like terms crossed off from each normal form until, hopefully, nothing remains. This cannot be complete, with the inclusion of unification variables. The type-checker can help you understand what goes wrong when the process fails, as it outputs the unmatched remainders of the two normal forms. + +\subsection{\label{typeclasses}Type Classes} + +Ur includes a type class facility inspired by Haskell's. The current version is very rudimentary, only supporting instances for particular types built up from abstract types and datatypes and type-level application. + +Type classes are integrated with the module system. A type class is just a constructor of kind $\mt{Type} \to \mt{Type}$. By marking such a constructor $c$ as a type class, the programmer instructs the type inference engine to, in each scope, record all values of types $c \; \tau$ as \emph{instances}. Any function argument whose type is of such a form is treated as implicit, to be determined by examining the current instance database. + +The ``dictionary encoding'' often used in Haskell implementations is made explicit in Ur. Type class instances are just properly-typed values, and they can also be considered as ``proofs'' of membership in the class. In some cases, it is useful to pass these proofs around explicitly. An underscore written where a proof is expected will also be inferred, if possible, from the current instance database. + +Just as for types, type classes may be exported from modules, and they may be exported as concrete or abstract. Concrete type classes have their ``real'' definitions exposed, so that client code may add new instances freely. Abstract type classes are useful as ``predicates'' that can be used to enforce invariants, as we will see in some definitions of SQL syntax in the Ur/Web standard library. + +\subsection{Reverse-Engineering Record Types} + +It's useful to write Ur functions and functors that take record constructors as inputs, but these constructors can grow quite long, even though their values are often implied by other arguments. The compiler uses a simple heuristic to infer the values of unification variables that are folded over, yielding known results. Often, as in the case of $\mt{map}$-like folds, the base and recursive cases of a fold produce constructors with different top-level structure. Thus, if the result of the fold is known, examining its top-level structure reveals whether the record being folded over is empty or not. If it's empty, we're done; if it's not empty, we replace a single unification variable with a new constructor formed from three new unification variables, as in $[\alpha = \beta] \rc \gamma$. This process can often be repeated to determine a unification variable fully. + +\subsection{Implicit Arguments in Functor Applications} + +Constructor, constraint, and type class witness members of structures may be omitted, when those structures are used in contexts where their assigned signatures imply how to fill in those missing members. This feature combines well with reverse-engineering to allow for uses of complicated meta-programming functors with little more code than would be necessary to invoke an untyped, ad-hoc code generator. + + \end{document} \ No newline at end of file -- cgit v1.2.3 From 718a8e9fc7cd60f227f56e6031c7e9ac054cb488 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 6 Dec 2008 13:04:48 -0500 Subject: Start of Ur/Web library --- doc/manual.tex | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 8ef6a889..894287e1 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -915,4 +915,51 @@ It's useful to write Ur functions and functors that take record constructors as Constructor, constraint, and type class witness members of structures may be omitted, when those structures are used in contexts where their assigned signatures imply how to fill in those missing members. This feature combines well with reverse-engineering to allow for uses of complicated meta-programming functors with little more code than would be necessary to invoke an untyped, ad-hoc code generator. +\section{The Ur Standard Library} + +The built-in parts of the Ur/Web standard library are described by the signature in \texttt{lib/basis.urs} in the distribution. A module $\mt{Basis}$ ascribing to that signature is available in the initial environment, and every program is implicitly prefixed by $\mt{open} \; \mt{Basis}$. + +Additionally, other common functions that are definable within Ur are included in \texttt{lib/top.urs} and \texttt{lib/top.ur}. This $\mt{Top}$ module is also opened implicitly. + +The idea behind Ur is to serve as the ideal host for embedded domain-specific languages. For now, however, the ``generic'' functionality is intermixed with Ur/Web-specific functionality, including in these two library modules. We hope that these generic library components have types that speak for themselves. The next section introduces the Ur/Web-specific elements. Here, we only give the type declarations from the beginning of $\mt{Basis}$. + +$$\begin{array}{l} + \mt{type} \; \mt{int} \\ + \mt{type} \; \mt{float} \\ + \mt{type} \; \mt{string} \\ + \mt{type} \; \mt{time} \\ + \\ + \mt{type} \; \mt{unit} = \{\} \\ + \\ + \mt{datatype} \; \mt{bool} = \mt{False} \mid \mt{True} \\ + \\ + \mt{datatype} \; \mt{option} \; \mt{t} = \mt{None} \mid \mt{Some} \; \mt{of} \; \mt{t} +\end{array}$$ + + +\section{The Ur/Web Standard Library} + +\subsection{Transactions} + +Ur is a pure language; we use Haskell's trick to support controlled side effects. The standard library defines a monad $\mt{transaction}$, meant to stand for actions that may be undone cleanly. By design, no other kinds of actions are supported. + +$$\begin{array}{l} + \mt{con} \; \mt{transaction} :: \mt{Type} \to \mt{Type} \\ + \\ + \mt{val} \; \mt{return} : \mt{t} ::: \mt{Type} \to \mt{t} \to \mt{transaction} \; \mt{t} \\ + \mt{val} \; \mt{bind} : \mt{t_1} ::: \mt{Type} \to \mt{t_2} ::: \mt{Type} \to \mt{transaction} \; \mt{t_1} \to (\mt{t_1} \to \mt{transaction} \; \mt{t_2}) \to \mt{transaction} \; \mt{t_2} +\end{array}$$ + +\subsection{HTTP} + +There are transactions for reading an HTTP header by name and for getting and setting strongly-typed cookies. Cookies may only be created by the $\mt{cookie}$ declaration form, ensuring that they be named consistently based on module structure. + +$$\begin{array}{l} +\mt{val} \; \mt{requestHeader} : \mt{string} \to \mt{transaction} \; (\mt{option} \; \mt{string}) \\ +\\ +\mt{con} \; \mt{http\_cookie} :: \mt{Type} \to \mt{Type} \\ +\mt{val} \; \mt{getCookie} : \mt{t} ::: \mt{Type} \to \mt{http\_cookie} \; \mt{t} \to \mt{transaction} \; (\mt{option} \; \mt{t}) \\ +\mt{val} \; \mt{setCookie} : \mt{t} ::: \mt{Type} \to \mt{http\_cookie} \; \mt{t} \to \mt{t} \to \mt{transaction} \; \mt{unit} +\end{array}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From 80bbc587e8c3e897cb30f0723187950254c6632b Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Dec 2008 09:19:53 -0500 Subject: Start of sql_exp --- doc/manual.tex | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 894287e1..0a0bdc88 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -962,4 +962,89 @@ $$\begin{array}{l} \mt{val} \; \mt{setCookie} : \mt{t} ::: \mt{Type} \to \mt{http\_cookie} \; \mt{t} \to \mt{t} \to \mt{transaction} \; \mt{unit} \end{array}$$ +\subsection{SQL} + +The fundamental unit of interest in the embedding of SQL is tables, described by a type family and creatable only via the $\mt{table}$ declaration form. + +$$\begin{array}{l} + \mt{con} \; \mt{sql\_table} :: \{\mt{Type}\} \to \mt{Type} +\end{array}$$ + +\subsubsection{Queries} + +A final query is constructed via the $\mt{sql\_query}$ function. Constructor arguments respectively specify the table fields we select (as records mapping tables to the subsets of their fields that we choose) and the (always named) extra expressions that we select. + +$$\begin{array}{l} + \mt{con} \; \mt{sql\_query} :: \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \\ + \mt{val} \; \mt{sql\_query} : \mt{tables} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{selectedFields} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{selectedExps} ::: \{\mt{Type}\} \\ + \hspace{.1in} \to \{\mt{Rows} : \mt{sql\_query1} \; \mt{tables} \; \mt{selectedFields} \; \mt{selectedExps}, \\ + \hspace{.2in} \mt{OrderBy} : \mt{sql\_order\_by} \; \mt{tables} \; \mt{selectedExps}, \\ + \hspace{.2in} \mt{Limit} : \mt{sql\_limit}, \\ + \hspace{.2in} \mt{Offset} : \mt{sql\_offset}\} \\ + \hspace{.1in} \to \mt{sql\_query} \; \mt{selectedFields} \; \mt{selectedExps} +\end{array}$$ + +Most of the complexity of the query encoding is in the type $\mt{sql\_query1}$, which includes simple queries and derived queries based on relational operators. Constructor arguments respectively specify the tables we select from, the subset of fields that we keep from each table for the result rows, and the extra expressions that we select. + +$$\begin{array}{l} + \mt{con} \; \mt{sql\_query1} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \\ + \\ + \mt{type} \; \mt{sql\_relop} \\ + \mt{val} \; \mt{sql\_union} : \mt{sql\_relop} \\ + \mt{val} \; \mt{sql\_intersect} : \mt{sql\_relop} \\ + \mt{val} \; \mt{sql\_except} : \mt{sql\_relop} \\ + \mt{val} \; \mt{sql\_relop} : \mt{tables1} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{tables2} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{selectedFields} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{selectedExps} ::: \{\mt{Type}\} \\ + \hspace{.1in} \to \mt{sql\_relop} \\ + \hspace{.1in} \to \mt{sql\_query1} \; \mt{tables1} \; \mt{selectedFields} \; \mt{selectedExps} \\ + \hspace{.1in} \to \mt{sql\_query1} \; \mt{tables2} \; \mt{selectedFields} \; \mt{selectedExps} \\ + \hspace{.1in} \to \mt{sql\_query1} \; \mt{selectedFields} \; \mt{selectedFields} \; \mt{selectedExps} +\end{array}$$ + +$$\begin{array}{l} + \mt{val} \; \mt{sql\_query1} : \mt{tables} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{grouped} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{selectedFields} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{selectedExps} ::: \{\mt{Type}\} \\ + \hspace{.1in} \to \{\mt{From} : \$(\mt{fold} \; (\lambda \mt{nm} \; (\mt{fields} :: \{\mt{Type}\}) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \mt{sql\_table} \; \mt{fields}] \rc \mt{acc}) \; [] \; \mt{tables}), \\ + \hspace{.2in} \mt{Where} : \mt{sql\_exp} \; \mt{tables} \; [] \; [] \; \mt{bool}, \\ + \hspace{.2in} \mt{GroupBy} : \mt{sql\_subset} \; \mt{tables} \; \mt{grouped}, \\ + \hspace{.2in} \mt{Having} : \mt{sql\_exp} \; \mt{grouped} \; \mt{tables} \; [] \; \mt{bool}, \\ + \hspace{.2in} \mt{SelectFields} : \mt{sql\_subset} \; \mt{grouped} \; \mt{selectedFields}, \\ + \hspace{.2in} \mt {SelectExps} : \$(\mt{fold} \; (\lambda \mt{nm} \; (\mt{t} :: \mt{Type}) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \mt{sql\_exp} \; \mt{grouped} \; \mt{tables} \; [] \; \mt{t}] \rc \mt{acc}) \; [] \; \mt{selectedExps}) \} \\ + \hspace{.1in} \to \mt{sql\_query1} \; \mt{tables} \; \mt{selectedFields} \; \mt{selectedExps} +\end{array}$$ + +To encode projection of subsets of fields in $\mt{SELECT}$ clauses, and to encode $\mt{GROUP} \; \mt{BY}$ clauses, we rely on a type family $\mt{sql\_subset}$, capturing what it means for one record of table fields to be a subset of another. The main constructor $\mt{sql\_subset}$ ``proves subset facts'' by requiring a split of a record into kept and dropped parts. The extra constructor $\mt{sql\_subset\_all}$ is a convenience for keeping all fields of a record. + +$$\begin{array}{l} + \mt{con} \; \mt{sql\_subset} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \mt{Type} \\ + \mt{val} \; \mt{sql\_subset} : \mt{keep\_drop} :: \{(\{\mt{Type}\} \times \{\mt{Type}\})\} \\ + \hspace{.1in} \to \mt{sql\_subset} \\ + \hspace{.2in} (\mt{fold} \; (\lambda \mt{nm} \; (\mt{fields} :: (\{\mt{Type}\} \times \{\mt{Type}\})) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \; [\mt{fields}.1 \sim \mt{fields}.2] \Rightarrow \\ + \hspace{.3in} [\mt{nm} = \mt{fields}.1 \rc \mt{fields}.2] \rc \mt{acc}) \; [] \; \mt{keep\_drop}) \\ + \hspace{.2in} (\mt{fold} \; (\lambda \mt{nm} \; (\mt{fields} :: (\{\mt{Type}\} \times \{\mt{Type}\})) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \mt{fields}.1] \rc \mt{acc}) \; [] \; \mt{keep\_drop}) \\ +\mt{val} \; \mt{sql\_subset\_all} : \mt{tables} :: \{\{\mt{Type}\}\} \to \mt{sql\_subset} \; \mt{tables} \; \mt{tables} +\end{array}$$ + +SQL expressions are used in several places, including $\mt{SELECT}$, $\mt{WHERE}$, $\mt{HAVING}$, and $\mt{ORDER} \; \mt{BY}$ clauses. They reify a fragment of the standard SQL expression language, while making it possible to inject ``native'' Ur values in some places. The arguments to the $\mt{sql\_exp}$ type family respectively give the unrestricted-availablity table fields, the table fields that may only be used in arguments to aggregate functions, the available selected expressions, and the type of the expression. + +$$\begin{array}{l} + \mt{con} \; \mt{sql\_exp} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \to \mt{Type} +\end{array}$$ + +Any field in scope may be converted to an expression. + +$$\begin{array}{l} + \mt{val} \; \mt{sql\_field} : \mt{otherTabs} ::: \{\{\mt{Type}\}\} \to \mt{otherFields} ::: \{\mt{Type}\} \\ + \hspace{.1in} \to \mt{fieldType} ::: \mt{Type} \to \mt{agg} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{exps} ::: \{\mt{Type}\} \\ + \hspace{.1in} \to \mt{tab} :: \mt{Name} \to \mt{field} :: \mt{Name} \\ + \hspace{.1in} \to \mt{sql\_exp} \; ([\mt{tab} = [\mt{field} = \mt{fieldType}] \rc \mt{otherFields}] \rc \mt{otherTabs}) \; \mt{agg} \; \mt{exps} \; \mt{fieldType} +\end{array}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From 6da109f29357054c27022d363819edd5da94206c Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Dec 2008 10:02:04 -0500 Subject: Finish documenting queries; remove a stray [unit] argument --- doc/manual.tex | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++----- lib/basis.urs | 2 +- src/monoize.sml | 3 +- src/urweb.grm | 3 +- 4 files changed, 116 insertions(+), 14 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 0a0bdc88..fb6b3b01 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -922,7 +922,6 @@ The built-in parts of the Ur/Web standard library are described by the signature Additionally, other common functions that are definable within Ur are included in \texttt{lib/top.urs} and \texttt{lib/top.ur}. This $\mt{Top}$ module is also opened implicitly. The idea behind Ur is to serve as the ideal host for embedded domain-specific languages. For now, however, the ``generic'' functionality is intermixed with Ur/Web-specific functionality, including in these two library modules. We hope that these generic library components have types that speak for themselves. The next section introduces the Ur/Web-specific elements. Here, we only give the type declarations from the beginning of $\mt{Basis}$. - $$\begin{array}{l} \mt{type} \; \mt{int} \\ \mt{type} \; \mt{float} \\ @@ -942,7 +941,6 @@ $$\begin{array}{l} \subsection{Transactions} Ur is a pure language; we use Haskell's trick to support controlled side effects. The standard library defines a monad $\mt{transaction}$, meant to stand for actions that may be undone cleanly. By design, no other kinds of actions are supported. - $$\begin{array}{l} \mt{con} \; \mt{transaction} :: \mt{Type} \to \mt{Type} \\ \\ @@ -953,7 +951,6 @@ $$\begin{array}{l} \subsection{HTTP} There are transactions for reading an HTTP header by name and for getting and setting strongly-typed cookies. Cookies may only be created by the $\mt{cookie}$ declaration form, ensuring that they be named consistently based on module structure. - $$\begin{array}{l} \mt{val} \; \mt{requestHeader} : \mt{string} \to \mt{transaction} \; (\mt{option} \; \mt{string}) \\ \\ @@ -965,7 +962,6 @@ $$\begin{array}{l} \subsection{SQL} The fundamental unit of interest in the embedding of SQL is tables, described by a type family and creatable only via the $\mt{table}$ declaration form. - $$\begin{array}{l} \mt{con} \; \mt{sql\_table} :: \{\mt{Type}\} \to \mt{Type} \end{array}$$ @@ -973,7 +969,6 @@ $$\begin{array}{l} \subsubsection{Queries} A final query is constructed via the $\mt{sql\_query}$ function. Constructor arguments respectively specify the table fields we select (as records mapping tables to the subsets of their fields that we choose) and the (always named) extra expressions that we select. - $$\begin{array}{l} \mt{con} \; \mt{sql\_query} :: \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \\ \mt{val} \; \mt{sql\_query} : \mt{tables} ::: \{\{\mt{Type}\}\} \\ @@ -987,7 +982,6 @@ $$\begin{array}{l} \end{array}$$ Most of the complexity of the query encoding is in the type $\mt{sql\_query1}$, which includes simple queries and derived queries based on relational operators. Constructor arguments respectively specify the tables we select from, the subset of fields that we keep from each table for the result rows, and the extra expressions that we select. - $$\begin{array}{l} \mt{con} \; \mt{sql\_query1} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \\ \\ @@ -1020,7 +1014,6 @@ $$\begin{array}{l} \end{array}$$ To encode projection of subsets of fields in $\mt{SELECT}$ clauses, and to encode $\mt{GROUP} \; \mt{BY}$ clauses, we rely on a type family $\mt{sql\_subset}$, capturing what it means for one record of table fields to be a subset of another. The main constructor $\mt{sql\_subset}$ ``proves subset facts'' by requiring a split of a record into kept and dropped parts. The extra constructor $\mt{sql\_subset\_all}$ is a convenience for keeping all fields of a record. - $$\begin{array}{l} \mt{con} \; \mt{sql\_subset} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \mt{Type} \\ \mt{val} \; \mt{sql\_subset} : \mt{keep\_drop} :: \{(\{\mt{Type}\} \times \{\mt{Type}\})\} \\ @@ -1032,13 +1025,11 @@ $$\begin{array}{l} \end{array}$$ SQL expressions are used in several places, including $\mt{SELECT}$, $\mt{WHERE}$, $\mt{HAVING}$, and $\mt{ORDER} \; \mt{BY}$ clauses. They reify a fragment of the standard SQL expression language, while making it possible to inject ``native'' Ur values in some places. The arguments to the $\mt{sql\_exp}$ type family respectively give the unrestricted-availablity table fields, the table fields that may only be used in arguments to aggregate functions, the available selected expressions, and the type of the expression. - $$\begin{array}{l} \mt{con} \; \mt{sql\_exp} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \to \mt{Type} \end{array}$$ Any field in scope may be converted to an expression. - $$\begin{array}{l} \mt{val} \; \mt{sql\_field} : \mt{otherTabs} ::: \{\{\mt{Type}\}\} \to \mt{otherFields} ::: \{\mt{Type}\} \\ \hspace{.1in} \to \mt{fieldType} ::: \mt{Type} \to \mt{agg} ::: \{\{\mt{Type}\}\} \\ @@ -1047,4 +1038,117 @@ $$\begin{array}{l} \hspace{.1in} \to \mt{sql\_exp} \; ([\mt{tab} = [\mt{field} = \mt{fieldType}] \rc \mt{otherFields}] \rc \mt{otherTabs}) \; \mt{agg} \; \mt{exps} \; \mt{fieldType} \end{array}$$ +There is an analogous function for referencing named expressions. +$$\begin{array}{l} + \mt{val} \; \mt{sql\_exp} : \mt{tabs} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{t} ::: \mt{Type} \to \mt{rest} ::: \{\mt{Type}\} \to \mt{nm} :: \mt{Name} \\ + \hspace{.1in} \to \mt{sql\_exp} \; \mt{tabs} \; \mt{agg} \; ([\mt{nm} = \mt{t}] \rc \mt{rest}) \; \mt{t} +\end{array}$$ + +Ur values of appropriate types may be injected into SQL expressions. +$$\begin{array}{l} + \mt{class} \; \mt{sql\_injectable} \\ + \mt{val} \; \mt{sql\_bool} : \mt{sql\_injectable} \; \mt{bool} \\ + \mt{val} \; \mt{sql\_int} : \mt{sql\_injectable} \; \mt{int} \\ + \mt{val} \; \mt{sql\_float} : \mt{sql\_injectable} \; \mt{float} \\ + \mt{val} \; \mt{sql\_string} : \mt{sql\_injectable} \; \mt{string} \\ + \mt{val} \; \mt{sql\_time} : \mt{sql\_injectable} \; \mt{time} \\ + \mt{val} \; \mt{sql\_option\_bool} : \mt{sql\_injectable} \; (\mt{option} \; \mt{bool}) \\ + \mt{val} \; \mt{sql\_option\_int} : \mt{sql\_injectable} \; (\mt{option} \; \mt{int}) \\ + \mt{val} \; \mt{sql\_option\_float} : \mt{sql\_injectable} \; (\mt{option} \; \mt{float}) \\ + \mt{val} \; \mt{sql\_option\_string} : \mt{sql\_injectable} \; (\mt{option} \; \mt{string}) \\ + \mt{val} \; \mt{sql\_option\_time} : \mt{sql\_injectable} \; (\mt{option} \; \mt{time}) \\ + \mt{val} \; \mt{sql\_inject} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \to \mt{sql\_injectable} \; \mt{t} \\ + \hspace{.1in} \to \mt{t} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{t} +\end{array}$$ + +We have the SQL nullness test, which is necessary because of the strange SQL semantics of equality in the presence of null values. +$$\begin{array}{l} + \mt{val} \; \mt{sql\_is\_null} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \\ + \hspace{.1in} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; (\mt{option} \; \mt{t}) \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{bool} +\end{array}$$ + +We have generic nullary, unary, and binary operators, as well as comparison operators. +$$\begin{array}{l} + \mt{con} \; \mt{sql\_nfunc} :: \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{sql\_current\_timestamp} : \mt{sql\_nfunc} \; \mt{time} \\ + \mt{val} \; \mt{sql\_nfunc} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \\ + \hspace{.1in} \to \mt{sql\_nfunc} \; \mt{t} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{t} \\\end{array}$$ + +$$\begin{array}{l} + \mt{con} \; \mt{sql\_unary} :: \mt{Type} \to \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{sql\_not} : \mt{sql\_unary} \; \mt{bool} \; \mt{bool} \\ + \mt{val} \; \mt{sql\_unary} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{arg} ::: \mt{Type} \to \mt{res} ::: \mt{Type} \\ + \hspace{.1in} \to \mt{sql\_unary} \; \mt{arg} \; \mt{res} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{arg} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{res} \\ +\end{array}$$ + +$$\begin{array}{l} + \mt{con} \; \mt{sql\_binary} :: \mt{Type} \to \mt{Type} \to \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{sql\_and} : \mt{sql\_binary} \; \mt{bool} \; \mt{bool} \; \mt{bool} \\ + \mt{val} \; \mt{sql\_or} : \mt{sql\_binary} \; \mt{bool} \; \mt{bool} \; \mt{bool} \\ + \mt{val} \; \mt{sql\_binary} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{arg_1} ::: \mt{Type} \to \mt{arg_2} ::: \mt{Type} \to \mt{res} ::: \mt{Type} \\ + \hspace{.1in} \to \mt{sql\_binary} \; \mt{arg_1} \; \mt{arg_2} \; \mt{res} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{arg_1} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{arg_2} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{res} +\end{array}$$ + +$$\begin{array}{l} + \mt{type} \; \mt{sql\_comparison} \\ + \mt{val} \; \mt{sql\_eq} : \mt{sql\_comparison} \\ + \mt{val} \; \mt{sql\_ne} : \mt{sql\_comparison} \\ + \mt{val} \; \mt{sql\_lt} : \mt{sql\_comparison} \\ + \mt{val} \; \mt{sql\_le} : \mt{sql\_comparison} \\ + \mt{val} \; \mt{sql\_gt} : \mt{sql\_comparison} \\ + \mt{val} \; \mt{sql\_ge} : \mt{sql\_comparison} \\ + \mt{val} \; \mt{sql\_comparison} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \\ + \hspace{.1in} \to \mt{sql\_comparison} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{t} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{t} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{bool} + \end{array}$$ + +Finally, we have aggregate functions. The $\mt{COUNT(\ast)}$ syntax is handled specially, since it takes no real argument. The other aggregate functions are placed into a general type family, using type classes to restrict usage to properly-typed arguments. The key aspect of the $\mt{sql\_aggregate}$ function's type is the shift of aggregate-function-only fields into unrestricted fields. + +$$\begin{array}{l} + \mt{val} \; \mt{sql\_count} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{int} +\end{array}$$ + +$$\begin{array}{l} + \mt{con} \; \mt{sql\_aggregate} :: \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{sql\_aggregate} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \\ + \hspace{.1in} \to \mt{sql\_aggregate} \; \mt{t} \to \mt{sql\_exp} \; \mt{agg} \; \mt{agg} \; \mt{exps} \; \mt{t} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{t} +\end{array}$$ + +$$\begin{array}{l} + \mt{class} \; \mt{sql\_summable} \\ + \mt{val} \; \mt{sql\_summable\_int} : \mt{sql\_summable} \; \mt{int} \\ + \mt{val} \; \mt{sql\_summable\_float} : \mt{sql\_summable} \; \mt{float} \\ + \mt{val} \; \mt{sql\_avg} : \mt{t} ::: \mt{Type} \to \mt{sql\_summable} \; \mt{t} \to \mt{sql\_aggregate} \; \mt{t} \\ + \mt{val} \; \mt{sql\_sum} : \mt{t} ::: \mt{Type} \to \mt{sql\_summable} \mt{t} \to \mt{sql\_aggregate} \; \mt{t} +\end{array}$$ + +$$\begin{array}{l} + \mt{class} \; \mt{sql\_maxable} \\ + \mt{val} \; \mt{sql\_maxable\_int} : \mt{sql\_maxable} \; \mt{int} \\ + \mt{val} \; \mt{sql\_maxable\_float} : \mt{sql\_maxable} \; \mt{float} \\ + \mt{val} \; \mt{sql\_maxable\_string} : \mt{sql\_maxable} \; \mt{string} \\ + \mt{val} \; \mt{sql\_maxable\_time} : \mt{sql\_maxable} \; \mt{time} \\ + \mt{val} \; \mt{sql\_max} : \mt{t} ::: \mt{Type} \to \mt{sql\_maxable} \; \mt{t} \to \mt{sql\_aggregate} \; \mt{t} \\ + \mt{val} \; \mt{sql\_min} : \mt{t} ::: \mt{Type} \to \mt{sql\_maxable} \; \mt{t} \to \mt{sql\_aggregate} \; \mt{t} +\end{array}$$ + +We wrap up the definition of query syntax with the types used in representing $\mt{ORDER} \; \mt{BY}$, $\mt{LIMIT}$, and $\mt{OFFSET}$ clauses. +$$\begin{array}{l} + \mt{type} \; \mt{sql\_direction} \\ + \mt{val} \; \mt{sql\_asc} : \mt{sql\_direction} \\ + \mt{val} \; \mt{sql\_desc} : \mt{sql\_direction} \\ + \\ + \mt{con} \; \mt{sql\_order\_by} :: \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \\ + \mt{val} \; \mt{sql\_order\_by\_Nil} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{exps} :: \{\mt{Type}\} \to \mt{sql\_order\_by} \; \mt{tables} \; \mt{exps} \\ + \mt{val} \; \mt{sql\_order\_by\_Cons} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \\ + \hspace{.1in} \to \mt{sql\_exp} \; \mt{tables} \; [] \; \mt{exps} \; \mt{t} \to \mt{sql\_direction} \to \mt{sql\_order\_by} \; \mt{tables} \; \mt{exps} \to \mt{sql\_order\_by} \; \mt{tables} \; \mt{exps} \\ + \\ + \mt{type} \; \mt{sql\_limit} \\ + \mt{val} \; \mt{sql\_no\_limit} : \mt{sql\_limit} \\ + \mt{val} \; \mt{sql\_limit} : \mt{int} \to \mt{sql\_limit} \\ + \\ + \mt{type} \; \mt{sql\_offset} \\ + \mt{val} \; \mt{sql\_no\_offset} : \mt{sql\_offset} \\ + \mt{val} \; \mt{sql\_offset} : \mt{int} \to \mt{sql\_offset} +\end{array}$$ + \end{document} \ No newline at end of file diff --git a/lib/basis.urs b/lib/basis.urs index 656c5b91..9681328f 100644 --- a/lib/basis.urs +++ b/lib/basis.urs @@ -232,7 +232,7 @@ val sql_comparison : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} -> sql_exp tables agg exps bool val sql_count : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} - -> unit -> sql_exp tables agg exps int + -> sql_exp tables agg exps int con sql_aggregate :: Type -> Type val sql_aggregate : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} diff --git a/src/monoize.sml b/src/monoize.sml index 28ea5946..cd20e366 100644 --- a/src/monoize.sml +++ b/src/monoize.sml @@ -1530,8 +1530,7 @@ fun monoExp (env, st, fm) (all as (e, loc)) = (L.EFfi ("Basis", "sql_count"), _), _), _), _), _), - _) => ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc), - (L'.EPrim (Prim.String "COUNT(*)"), loc)), loc), + _) => ((L'.EPrim (Prim.String "COUNT(*)"), loc), fm) | L.ECApp ( diff --git a/src/urweb.grm b/src/urweb.grm index 8a3bee7f..3d77905e 100644 --- a/src/urweb.grm +++ b/src/urweb.grm @@ -1267,8 +1267,7 @@ sqlexp : TRUE (sql_inject (EVar (["Basis"], "True", In | COUNT LPAREN STAR RPAREN (let val loc = s (COUNTleft, RPARENright) in - (EApp ((EVar (["Basis"], "sql_count", Infer), loc), - (ERecord [], loc)), loc) + (EVar (["Basis"], "sql_count", Infer), loc) end) | sqlagg LPAREN sqlexp RPAREN (let val loc = s (sqlaggleft, RPARENright) -- cgit v1.2.3 From d86935ec25586bbba5b6aaf60fb93d20e99de964 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Dec 2008 10:24:23 -0500 Subject: DML --- doc/manual.tex | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index fb6b3b01..83ce8867 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -981,6 +981,14 @@ $$\begin{array}{l} \hspace{.1in} \to \mt{sql\_query} \; \mt{selectedFields} \; \mt{selectedExps} \end{array}$$ +Queries are used by folding over their results inside transactions. +$$\begin{array}{l} + \mt{val} \; \mt{query} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \lambda [\mt{tables} \sim \mt{exps}] \Rightarrow \mt{state} ::: \mt{Type} \to \mt{sql\_query} \; \mt{tables} \; \mt{exps} \\ + \hspace{.1in} \to (\$(\mt{exps} \rc \mt{fold} \; (\lambda \mt{nm} \; (\mt{fields} :: \{\mt{Type}\}) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \$\mt{fields}] \rc \mt{acc}) \; [] \; \mt{tables}) \\ + \hspace{.2in} \to \mt{state} \to \mt{transaction} \; \mt{state}) \\ + \hspace{.1in} \to \mt{state} \to \mt{transaction} \; \mt{state} +\end{array}$$ + Most of the complexity of the query encoding is in the type $\mt{sql\_query1}$, which includes simple queries and derived queries based on relational operators. Constructor arguments respectively specify the tables we select from, the subset of fields that we keep from each table for the result rows, and the extra expressions that we select. $$\begin{array}{l} \mt{con} \; \mt{sql\_query1} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \\ @@ -1151,4 +1159,32 @@ $$\begin{array}{l} \mt{val} \; \mt{sql\_offset} : \mt{int} \to \mt{sql\_offset} \end{array}$$ + +\subsubsection{DML} + +The Ur/Web library also includes an embedding of a fragment of SQL's DML, the Data Manipulation Language, for modifying database tables. Any piece of DML may be executed in a transaction. + +$$\begin{array}{l} + \mt{type} \; \mt{dml} \\ + \mt{val} \; \mt{dml} : \mt{dml} \to \mt{transaction} \; \mt{unit} +\end{array}$$ + +Properly-typed records may be used to form $\mt{INSERT}$ commands. +$$\begin{array}{l} + \mt{val} \; \mt{insert} : \mt{fields} ::: \{\mt{Type}\} \to \mt{sql\_table} \; \mt{fields} \\ + \hspace{.1in} \to \$(\mt{fold} \; (\lambda \mt{nm} \; (\mt{t} :: \mt{Type}) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \mt{sql\_exp} \; [] \; [] \; [] \; \mt{t}] \rc \mt{acc}) \; [] \; \mt{fields}) \to \mt{dml} +\end{array}$$ + +An $\mt{UPDATE}$ command is formed from a choice of which table fields to leave alone and which to change, along with an expression to use to compute the new value of each changed field and a $\mt{WHERE}$ clause. +$$\begin{array}{l} + \mt{val} \; \mt{update} : \mt{unchanged} ::: \{\mt{Type}\} \to \mt{changed} :: \{\mt{Type}\} \to \lambda [\mt{changed} \sim \mt{unchanged}] \\ + \hspace{.1in} \Rightarrow \$(\mt{fold} \; (\lambda \mt{nm} \; (\mt{t} :: \mt{Type}) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \mt{sql\_exp} \; [\mt{T} = \mt{changed} \rc \mt{unchanged}] \; [] \; [] \; \mt{t}] \rc \mt{acc}) \; [] \; \mt{changed}) \\ + \hspace{.1in} \to \mt{sql\_table} \; (\mt{changed} \rc \mt{unchanged}) \to \mt{sql\_exp} \; [\mt{T} = \mt{changed} \rc \mt{unchanged}] \; [] \; [] \; \mt{bool} \to \mt{dml} +\end{array}$$ + +A $\mt{DELETE}$ command is formed from a table and a $\mt{WHERE}$ clause. +$$\begin{array}{l} + \mt{val} \; \mt{delete} : \mt{fields} ::: \{\mt{Type}\} \to \mt{sql\_table} \; \mt{fields} \to \mt{sql\_exp} \; [\mt{T} = \mt{fields}] \; [] \; [] \; \mt{bool} \to \mt{dml} +\end{array}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From 41c63800f3c6f330002b29b133836f6e4f7a81d3 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Dec 2008 10:25:55 -0500 Subject: Sequences --- doc/manual.tex | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 83ce8867..95d2d548 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1187,4 +1187,14 @@ $$\begin{array}{l} \mt{val} \; \mt{delete} : \mt{fields} ::: \{\mt{Type}\} \to \mt{sql\_table} \; \mt{fields} \to \mt{sql\_exp} \; [\mt{T} = \mt{fields}] \; [] \; [] \; \mt{bool} \to \mt{dml} \end{array}$$ +\subsubsection{Sequences} + +SQL sequences are counters with concurrency control, often used to assign unique IDs. Ur/Web supports them via a simple interface. The only way to create a sequence is with the $\mt{sequence}$ declaration form. + +$$\begin{array}{l} + \mt{type} \; \mt{sql\_sequence} \\ + \mt{val} \; \mt{nextval} : \mt{sql\_sequence} \to \mt{transaction} \; \mt{int} +\end{array}$$ + + \end{document} \ No newline at end of file -- cgit v1.2.3 From fe138022197bc0dede592fc1df97e1ef540c1b6a Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Dec 2008 10:59:14 -0500 Subject: XML --- doc/manual.tex | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 95d2d548..0dc33a4d 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1197,4 +1197,52 @@ $$\begin{array}{l} \end{array}$$ +\subsection{XML} + +Ur/Web's library contains an encoding of XML syntax and semantic constraints. We make no effort to follow the standards governing XML schemas. Rather, XML fragments are viewed more as values of ML datatypes, and we only track which tags are allowed inside which other tags. + +The basic XML type family has arguments respectively indicating the \emph{context} of a fragment, the fields that the fragment expects to be bound on entry (and their types), and the fields that the fragment will bind (and their types). Contexts are a record-based ``poor man's subtyping'' encoding, with each possible set of valid tags corresponding to a different context record. The arguments dealing with field binding are only relevant to HTML forms. +$$\begin{array}{l} + \mt{con} \; \mt{xml} :: \{\mt{Unit}\} \to \{\mt{Type}\} \to \{\mt{Type}\} \to \mt{Type} +\end{array}$$ + +We also have a type family of XML tags, indexed respectively by the record of optional attributes accepted by the tag, the context in which the tag may be placed, the context required of children of the tag, which form fields the tag uses, and which fields the tag defines. +$$\begin{array}{l} + \mt{con} \; \mt{tag} :: \{\mt{Type}\} \to \{\mt{Unit}\} \to \{\mt{Unit}\} \to \{\mt{Type}\} \to \{\mt{Type}\} \to \mt{Type} +\end{array}$$ + +Literal text may be injected into XML as ``CDATA.'' +$$\begin{array}{l} + \mt{val} \; \mt{cdata} : \mt{ctx} ::: \{\mt{Unit}\} \to \mt{use} ::: \{\mt{Type}\} \to \mt{string} \to \mt{xml} \; \mt{ctx} \; \mt{use} \; [] +\end{array}$$ + +There is a function for producing an XML tree with a particular tag at its root. +$$\begin{array}{l} + \mt{val} \; \mt{tag} : \mt{attrsGiven} ::: \{\mt{Type}\} \to \mt{attrsAbsent} ::: \{\mt{Type}\} \to \mt{ctxOuter} ::: \{\mt{Unit}\} \to \mt{ctxInner} ::: \{\mt{Unit}\} \\ + \hspace{.1in} \to \mt{useOuter} ::: \{\mt{Type}\} \to \mt{useInner} ::: \{\mt{Type}\} \to \mt{bindOuter} ::: \{\mt{Type}\} \to \mt{bindInner} ::: \{\mt{Type}\} \\ + \hspace{.1in} \to \lambda [\mt{attrsGiven} \sim \mt{attrsAbsent}] \; [\mt{useOuter} \sim \mt{useInner}] \; [\mt{bindOuter} \sim \mt{bindInner}] \Rightarrow \$\mt{attrsGiven} \\ + \hspace{.1in} \to \mt{tag} \; (\mt{attrsGiven} \rc \mt{attrsAbsent}) \; \mt{ctxOuter} \; \mt{ctxInner} \; \mt{useOuter} \; \mt{bindOuter} \\ + \hspace{.1in} \to \mt{xml} \; \mt{ctxInner} \; \mt{useInner} \; \mt{bindInner} \to \mt{xml} \; \mt{ctxOuter} \; (\mt{useOuter} \rc \mt{useInner}) \; (\mt{bindOuter} \rc \mt{bindInner}) +\end{array}$$ + +Two XML fragments may be concatenated. +$$\begin{array}{l} + \mt{val} \; \mt{join} : \mt{ctx} ::: \{\mt{Unit}\} \to \mt{use_1} ::: \{\mt{Type}\} \to \mt{bind_1} ::: \{\mt{Type}\} \to \mt{bind_2} ::: \{\mt{Type}\} \\ + \hspace{.1in} \to \lambda [\mt{use_1} \sim \mt{bind_1}] \; [\mt{bind_1} \sim \mt{bind_2}] \\ + \hspace{.1in} \Rightarrow \mt{xml} \; \mt{ctx} \; \mt{use_1} \; \mt{bind_1} \to \mt{xml} \; \mt{ctx} \; (\mt{use_1} \rc \mt{bind_1}) \; \mt{bind_2} \to \mt{xml} \; \mt{ctx} \; \mt{use_1} \; (\mt{bind_1} \rc \mt{bind_2}) +\end{array}$$ + +Finally, any XML fragment may be updated to ``claim'' to use more form fields than it does. +$$\begin{array}{l} + \mt{val} \; \mt{useMore} : \mt{ctx} ::: \{\mt{Unit}\} \to \mt{use_1} ::: \{\mt{Type}\} \to \mt{use_2} ::: \{\mt{Type}\} \to \mt{bind} ::: \{\mt{Type}\} \to \lambda [\mt{use_1} \sim \mt{use_2}] \\ + \hspace{.1in} \Rightarrow \mt{xml} \; \mt{ctx} \; \mt{use_1} \; \mt{bind} \to \mt{xml} \; \mt{ctx} \; (\mt{use_1} \rc \mt{use_2}) \; \mt{bind} +\end{array}$$ + +We will not list here the different HTML tags and related functions from the standard library. They should be easy enough to understand from the code in \texttt{basis.urs}. The set of tags in the library is not yet claimed to be complete for HTML standards. + +One last useful function is for aborting any page generation, returning some XML as an error message. This function takes the place of some uses of a general exception mechanism. +$$\begin{array}{l} + \mt{val} \; \mt{error} : \mt{t} ::: \mt{Type} \to \mt{xml} \; [\mt{Body}] \; [] \; [] \to \mt{t} +\end{array}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From b45bf9b187a61b2a803555025e1d6496144a9759 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Dec 2008 12:02:54 -0500 Subject: Query syntax --- doc/manual.tex | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 0dc33a4d..79cda554 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1245,4 +1245,58 @@ $$\begin{array}{l} \mt{val} \; \mt{error} : \mt{t} ::: \mt{Type} \to \mt{xml} \; [\mt{Body}] \; [] \; [] \to \mt{t} \end{array}$$ + +\section{Ur/Web Syntax Extensions} + +Ur/Web features some syntactic shorthands for building values using the functions from the last section. This section sketches the grammar of those extensions. We write spans of syntax inside brackets to indicate that they are optional. + +\subsection{SQL} + +\subsubsection{Queries} + +$$\begin{array}{rrcll} + \textrm{Queries} & Q &::=& (q \; [\mt{ORDER} \; \mt{BY} \; (E \; [D],)^+] \; [\mt{LIMIT} \; N] \; [\mt{OFFSET} \; N]) \\ + \textrm{Pre-queries} & q &::=& \mt{SELECT} \; P \; \mt{FROM} \; T,^+ \; [\mt{WHERE} \; E] \; [\mt{GROUP} \; \mt{BY} \; p,^+] \; [\mt{HAVING} \; E] \\ + &&& \mid q \; R \; q \\ + \textrm{Relational operators} & R &::=& \mt{UNION} \mid \mt{INTERSECT} \mid \mt{EXCEPT} +\end{array}$$ + +$$\begin{array}{rrcll} + \textrm{Projections} & P &::=& \ast & \textrm{all columns} \\ + &&& p,^+ & \textrm{particular columns} \\ + \textrm{Pre-projections} & p &::=& t.f & \textrm{one column from a table} \\ + &&& t.\{\{c\}\} & \textrm{a record of colums from a table (of kind $\{\mt{Type}\}$)} \\ + \textrm{Table names} & t &::=& x & \textrm{constant table name (automatically capitalized)} \\ + &&& X & \textrm{constant table name} \\ + &&& \{\{c\}\} & \textrm{computed table name (of kind $\mt{Name}$)} \\ + \textrm{Column names} & f &::=& X & \textrm{constant column name} \\ + &&& \{c\} & \textrm{computed column name (of kind $\mt{Name}$)} \\ + \textrm{Tables} & T &::=& x & \textrm{table variable, named locally by its own capitalization} \\ + &&& x \; \mt{AS} \; t & \textrm{table variable, with local name} \\ + &&& \{\{e\}\} \; \mt{AS} \; t & \textrm{computed table expression, with local name} \\ + \textrm{SQL expressions} & E &::=& p & \textrm{column references} \\ + &&& X & \textrm{named expression references} \\ + &&& \{\{e\}\} & \textrm{injected native Ur expressions} \\ + &&& \{e\} & \textrm{computed expressions, probably using $\mt{sql\_exp}$ directly} \\ + &&& \mt{TRUE} \mid \mt{FALSE} & \textrm{boolean constants} \\ + &&& \ell & \textrm{primitive type literals} \\ + &&& \mt{NULL} & \textrm{null value (injection of $\mt{None}$)} \\ + &&& E \; \mt{IS} \; \mt{NULL} & \textrm{nullness test} \\ + &&& n & \textrm{nullary operators} \\ + &&& u \; E & \textrm{unary operators} \\ + &&& E \; b \; E & \textrm{binary operators} \\ + &&& \mt{COUNT}(\ast) & \textrm{count number of rows} \\ + &&& a(E) & \textrm{other aggregate function} \\ + &&& (E) & \textrm{explicit precedence} \\ + \textrm{Nullary operators} & n &::=& \mt{CURRENT\_TIMESTAMP} \\ + \textrm{Unary operators} & u &::=& \mt{NOT} \\ + \textrm{Binary operators} & b &::=& \mt{AND} \mid \mt{OR} \mid \neq \mid < \mid \leq \mid > \mid \geq \\ + \textrm{Aggregate functions} & a &::=& \mt{AVG} \mid \mt{SUM} \mid \mt{MIN} \mid \mt{MAX} \\ + \textrm{Directions} & D &::=& \mt{ASC} \mid \mt{DESC} \\ + \textrm{SQL integer} & N &::=& n \mid \{e\} \\ +\end{array}$$ + +Additionally, an SQL expression may be inserted into normal Ur code with the syntax $(\mt{SQL} \; E)$ or $(\mt{WHERE} \; E)$. + + \end{document} \ No newline at end of file -- cgit v1.2.3 From 4532add1a287aa8922ba5d0d556db3cd04e42420 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Dec 2008 12:10:51 -0500 Subject: DML --- doc/manual.tex | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 79cda554..4915edfb 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1254,8 +1254,10 @@ Ur/Web features some syntactic shorthands for building values using the function \subsubsection{Queries} +Queries $Q$ are added to the rules for expressions $e$. + $$\begin{array}{rrcll} - \textrm{Queries} & Q &::=& (q \; [\mt{ORDER} \; \mt{BY} \; (E \; [D],)^+] \; [\mt{LIMIT} \; N] \; [\mt{OFFSET} \; N]) \\ + \textrm{Queries} & Q &::=& (q \; [\mt{ORDER} \; \mt{BY} \; (E \; [o],)^+] \; [\mt{LIMIT} \; N] \; [\mt{OFFSET} \; N]) \\ \textrm{Pre-queries} & q &::=& \mt{SELECT} \; P \; \mt{FROM} \; T,^+ \; [\mt{WHERE} \; E] \; [\mt{GROUP} \; \mt{BY} \; p,^+] \; [\mt{HAVING} \; E] \\ &&& \mid q \; R \; q \\ \textrm{Relational operators} & R &::=& \mt{UNION} \mid \mt{INTERSECT} \mid \mt{EXCEPT} @@ -1292,11 +1294,23 @@ $$\begin{array}{rrcll} \textrm{Unary operators} & u &::=& \mt{NOT} \\ \textrm{Binary operators} & b &::=& \mt{AND} \mid \mt{OR} \mid \neq \mid < \mid \leq \mid > \mid \geq \\ \textrm{Aggregate functions} & a &::=& \mt{AVG} \mid \mt{SUM} \mid \mt{MIN} \mid \mt{MAX} \\ - \textrm{Directions} & D &::=& \mt{ASC} \mid \mt{DESC} \\ + \textrm{Directions} & o &::=& \mt{ASC} \mid \mt{DESC} \\ \textrm{SQL integer} & N &::=& n \mid \{e\} \\ \end{array}$$ Additionally, an SQL expression may be inserted into normal Ur code with the syntax $(\mt{SQL} \; E)$ or $(\mt{WHERE} \; E)$. +\subsubsection{DML} + +DML commands $D$ are added to the rules for expressions $e$. + +$$\begin{array}{rrcll} + \textrm{Commands} & D &::=& (\mt{INSERT} \; \mt{INTO} \; T^E \; (f,^+) \; \mt{VALUES} \; (E,^+)) \\ + &&& (\mt{UPDATE} \; T^E \; \mt{SET} \; (f = E,)^+ \; \mt{WHERE} \; E) \\ + &&& (\mt{DELETE} \; \mt{FROM} \; T^E \; \mt{WHERE} \; E) \\ + \textrm{Table expressions} & T^E &::=& x \mid \{\{e\}\} +\end{array}$$ + +Inside $\mt{UPDATE}$ and $\mt{DELETE}$ commands, lone variables $X$ are interpreted as references to columns of the implicit table $\mt{T}$, rather than to named expressions. \end{document} \ No newline at end of file -- cgit v1.2.3 From 13eec52acec99c062f98a80b38c590ad7adfd8b9 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Dec 2008 12:21:47 -0500 Subject: XML syntax --- doc/manual.tex | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 4915edfb..b52146de 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1313,4 +1313,20 @@ $$\begin{array}{rrcll} Inside $\mt{UPDATE}$ and $\mt{DELETE}$ commands, lone variables $X$ are interpreted as references to columns of the implicit table $\mt{T}$, rather than to named expressions. +\subsection{XML} + +XML fragments $L$ are added to the rules for expressions $e$. + +$$\begin{array}{rrcll} + \textrm{XML fragments} & L &::=& \texttt{} \mid \texttt{}l^*\texttt{} \\ + \textrm{XML pieces} & l &::=& \textrm{text} & \textrm{cdata} \\ + &&& \texttt{<}g\texttt{/>} & \textrm{tag with no children} \\ + &&& \texttt{<}g\texttt{>}l^*\texttt{} & \textrm{tag with children} \\ + \textrm{Tag} & g &::=& h \; (x = v)^* \\ + \textrm{Tag head} & h &::=& x & \textrm{tag name} \\ + &&& h\{c\} & \textrm{constructor parameter} \\ + \textrm{Attribute value} & v &::=& \ell & \textrm{literal value} \\ + &&& \{e\} & \textrm{computed value} \\ +\end{array}$$ + \end{document} \ No newline at end of file -- cgit v1.2.3 From da893776fddf8136a4b8ae6cfcb536e0fe6863ca Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Dec 2008 14:50:03 -0500 Subject: Compiler phases --- doc/manual.tex | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index b52146de..12939a56 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -47,7 +47,7 @@ We write $\ell$ for literals of the primitive types, for the most part following This version of the manual doesn't include operator precedences; see \texttt{src/urweb.grm} for that. -\subsection{Core Syntax} +\subsection{\label{core}Core Syntax} \emph{Kinds} classify types and other compile-time-only entities. Each kind in the grammar is listed with a description of the sort of data it classifies. $$\begin{array}{rrcll} @@ -1329,4 +1329,101 @@ $$\begin{array}{rrcll} &&& \{e\} & \textrm{computed value} \\ \end{array}$$ + +\section{Compiler Phases} + +The Ur/Web compiler is unconventional in that it relies on a kind of \emph{heuristic compilation}. Not all valid programs will compile successfully. Informally, programs fail to compile when they are ``too higher order.'' Compiler phases do their best to eliminate different kinds of higher order-ness, but some programs just won't compile. This is a trade-off for producing very efficient executables. Compiled Ur/Web programs use native C representations and require no garbage collection. + +In this section, we step through the main phases of compilation, noting what consequences each phase has for effective programming. + +\subsection{Parse} + +The compiler reads a \texttt{.urp} file, figures out which \texttt{.urs} and \texttt{.ur} files it references, and combines them all into what is conceptually a single sequence of declarations in the core language of Section \ref{core}. + +\subsection{Elaborate} + +This is where type inference takes place, translating programs into an explicit form with no more wildcards. This phase is the most likely source of compiler error messages. + +\subsection{Unnest} + +Named local function definitions are moved to the top level, to avoid the need to generate closures. + +\subsection{Corify} + +Module system features are compiled away, through inlining of functor definitions at application sites. Afterward, most abstraction boundaries are broken, facilitating optimization. + +\subsection{Especialize} + +Functions are specialized to particular argument patterns. This is an important trick for avoiding the need to maintain any closures at runtime. + +\subsection{Untangle} + +Remove unnecessary mutual recursion, splitting recursive groups into strongly-connected components. + +\subsection{Shake} + +Remove all definitions not needed to run the page handlers that are visible in the signature of the last module listed in the \texttt{.urp} file. + +\subsection{Tag} + +Assign a URL name to each link and form action. It is important that these links and actions are written as applications of named functions, because such names are used to generate URL patterns. A URL pattern has a name built from the full module path of the named function, followed by the function name, with all pieces separated by slashes. The path of a functor application is based on the name given to the result, rather than the path of the functor itself. + +\subsection{Reduce} + +Apply definitional equality rules to simplify the program as much as possible. This effectively includes inlining of every non-recursive definition. + +\subsection{Unpoly} + +This phase specializes polymorphic functions to the specific arguments passed to them in the program. If the program contains real polymorphic recursion, Unpoly will be insufficient to avoid later error messages about too much polymorphism. + +\subsection{Specialize} + +Replace uses of parametrized datatypes with versions specialized to specific parameters. As for Unpoly, this phase will not be effective enough in the presence of polymorphic recursion or other fancy uses of impredicative polymorphism. + +\subsection{Shake} + +Here the compiler repeats the earlier shake phase. + +\subsection{Monoize} + +Programs are translated to a new intermediate language without polymorphism or non-$\mt{Type}$ constructors. Error messages may pop up here if earlier phases failed to remove such features. + +This is the stage at which concrete names are generated for cookies, tables, and sequences. They are named following the same convention as for links and actions, based on module path information saved from earlier stages. Table and sequence names separate path elements with underscores instead of slashes, and they are prefixed by \texttt{uw\_}. +\subsection{MonoOpt} + +Simple algebraic laws are applied to simplify the program, focusing especially on efficient imperative generation of HTML pages. + +\subsection{MonoUntangle} + +Unnecessary mutual recursion is broken up again. + +\subsection{MonoReduce} + +Equivalents of the definitional equality rules are applied to simplify programs, with inlining again playing a major role. + +\subsection{MonoShake, MonoOpt} + +Unneeded declarations are removed, and basic optimizations are repeated. + +\subsection{Fuse} + +The compiler tries to simplify calls to recursive functions whose results are immediately written as page output. The write action is pushed inside the function definitions to avoid allocation of intermediate results. + +\subsection{MonoUntangle, MonoShake} + +Fuse often creates more opportunities to remove spurious mutual recursion. + +\subsection{Pathcheck} + +The compiler checks that no link or action name has been used more than once. + +\subsection{Cjrize} + +The program is translated to what is more or less a subset of C. If any use of functions as data remains at this point, the compiler will complain. + +\subsection{C Compilation and Linking} + +The output of the last phase is pretty-printed as C source code and passed to GCC. + + \end{document} \ No newline at end of file -- cgit v1.2.3 From ca1b68736e14dff52c08e76a7a6dfa855d1884f9 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Dec 2008 15:01:21 -0500 Subject: The structure of web applications --- doc/manual.tex | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 12939a56..46404f7c 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1330,6 +1330,17 @@ $$\begin{array}{rrcll} \end{array}$$ +\section{The Structure of Web Applications} + +A web application is built from a series of modules, with one module, the last one appearing in the \texttt{.urp} file, designated as the main module. The signature of the main module determines the URL entry points to the application. Such an entry point should have type $\mt{unit} \to \mt{transaction} \; \mt{page}$, where $\mt{page}$ is a type synonym for top-level HTML pages, defined in $\mt{Basis}$. If such a function is at the top level of main module $M$, it will be accessible at URI \texttt{/M/f}, and so on for more deeply-nested functions, as described in Section \ref{tag} below. + +When the standalone web server receives a request for a known page, it calls the function for that page, ``running'' the resulting transaction to produce the page to return to the client. Pages link to other pages with the \texttt{link} attribute of the \texttt{a} HTML tag. A link has type $\mt{transaction} \; \mt{page}$, and the semantics of a link are that this transaction should be run to compute the result page, when the link is followed. Link targets are assigned URL names in the same way as top-level entry points. + +HTML forms are handled in a similar way. The $\mt{action}$ attribute of a $\mt{submit}$ form tag takes a value of type $\$\mt{use} \to \mt{transaction} \; \mt{page}$, where $\mt{use}$ is a kind-$\{\mt{Type}\}$ record of the form fields used by this action handler. Action handlers are assigned URL patterns in the same way as above. + +For both links and actions, direct arguments and local variables mentioned implicitly via closures are automatically included in serialized form in URLs, in the order in which they appeared in the source code. + + \section{Compiler Phases} The Ur/Web compiler is unconventional in that it relies on a kind of \emph{heuristic compilation}. Not all valid programs will compile successfully. Informally, programs fail to compile when they are ``too higher order.'' Compiler phases do their best to eliminate different kinds of higher order-ness, but some programs just won't compile. This is a trade-off for producing very efficient executables. Compiled Ur/Web programs use native C representations and require no garbage collection. @@ -1364,7 +1375,7 @@ Remove unnecessary mutual recursion, splitting recursive groups into strongly-co Remove all definitions not needed to run the page handlers that are visible in the signature of the last module listed in the \texttt{.urp} file. -\subsection{Tag} +\subsection{\label{tag}Tag} Assign a URL name to each link and form action. It is important that these links and actions are written as applications of named functions, because such names are used to generate URL patterns. A URL pattern has a name built from the full module path of the named function, followed by the function name, with all pieces separated by slashes. The path of a functor application is based on the name given to the result, rather than the path of the functor itself. -- cgit v1.2.3 From a317e5050fe88a8672a5da5faa2d7180ab285a0d Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Dec 2008 15:10:59 -0500 Subject: Intro --- doc/manual.tex | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 46404f7c..8d507792 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1,5 +1,5 @@ \documentclass{article} -\usepackage{fullpage,amsmath,amssymb,proof} +\usepackage{fullpage,amsmath,amssymb,proof,url} \newcommand{\cd}[1]{\texttt{#1}} \newcommand{\mt}[1]{\mathsf{#1}} @@ -17,6 +17,33 @@ \tableofcontents + +\section{Introduction} + +\emph{Ur} is a programming language designed to introduce richer type system features into functional programming in the tradition of ML and Haskell. Ur is functional, pure, statically-typed, and strict. Ur supports a powerful kind of \emph{metaprogramming} based on \emph{row types}. + +\emph{Ur/Web} is Ur plus a special standard library and associated rules for parsing and optimization. Ur/Web supports construction of dynamic web applications backed by SQL databases. The signature of the standard library is such that well-typed Ur/Web programs ``don't go wrong'' in a very broad sense. Not only do they not crash during particular page generations, but they also may not: + +\begin{itemize} +\item Suffer from any kinds of code-injection attacks +\item Return invalid HTML +\item Contain dead intra-application links +\item Have mismatches between HTML forms and the fields expected by their handlers +\item Attempt invalid SQL queries +\item Use improper marshaling or unmarshaling in communication with SQL databases +\end{itemize} + +This type safety is just the foundation of the Ur/Web methodology. It is also possible to use metaprogramming to build significant application pieces by analysis of type structure. For instance, the demo includes an ML-style functor for building an admin interface for an arbitrary SQL table. The type system guarantees that the admin interface sub-application that comes out will always be free of the above-listed bugs, no matter which well-typed table description is given as input. + +The Ur/Web compiler also produces very efficient object code that does not use garbage collection. These compiled programs will often be even more efficient than what most programmers would bother to write in C. + +\medskip + +The official web site for Ur is: +\begin{center} + \url{http://www.impredicative.com/ur/} +\end{center} + \section{Ur Syntax} In this section, we describe the syntax of Ur, deferring to a later section discussion of most of the syntax specific to SQL and XML. The sole exceptions are the declaration forms for tables, sequences, and cookies. -- cgit v1.2.3 From d3a3f5f7e087580215f82afe90a4f64f1a75ebc1 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 9 Dec 2008 11:40:51 -0500 Subject: Installation --- doc/manual.tex | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 8d507792..942cee77 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -44,6 +44,61 @@ The official web site for Ur is: \url{http://www.impredicative.com/ur/} \end{center} + +\section{Installation} + +If you are lucky, then the following standard command sequence will suffice for installation, in a directory to which you have unpacked the latest distribution tarball. + +\begin{verbatim} +./configure +make +sudo make install +\end{verbatim} + +Some other packages must be installed for the above to work. At a minimum, you need a standard UNIX shell, with standard UNIX tools like sed and GCC in your execution path; and MLton, the whole-program optimizing compiler for Standard ML. To build programs that access SQL databases, you also need libpq, the PostgreSQL client library. As of this writing, in the ``testing'' version of Debian Linux, this command will install the more uncommon of these dependencies: + +\begin{verbatim} +apt-get install mlton libpq-dev +\end{verbatim} + +It is also possible to access the modules of the Ur/Web compiler interactively, within Standard ML of New Jersey. To install the prerequisites in Debian testing: + +\begin{verbatim} +apt-get install smlnj libsmlnj-smlnj ml-yacc ml-lpt +\end{verbatim} + +To begin an interactive session with the Ur compiler modules, run \texttt{make smlnj}, and then, from within an \texttt{sml} session, run \texttt{CM.make "src/urweb.cm";}. The \texttt{Compiler} module is the main entry point. + +To run an SQL-backed application, you will probably want to install the PostgreSQL server. Version 8.3 or higher is required. + +\begin{verbatim} +apt-get install postgresql-8.3 +\end{verbatim} + +To use the Emacs mode, you must have a modern Emacs installed. We assume that you already know how to do this, if you're in the business of looking for an Emacs mode. The demo generation facility of the compiler will also call out to Emacs to syntax-highlight code, and that process depends on the \texttt{htmlize} module, which can be installed in Debian testing via: + +\begin{verbatim} +apt-get install emacs-goodies-el +\end{verbatim} + +Even with the right packages installed, configuration and building might fail to work. After you run \texttt{./configure}, you will see the values of some named environment variables printed. You may need to adjust these values to get proper installation for your system. To change a value, store your preferred alternative in the corresponding UNIX environment variable, before running \texttt{./configure}. For instance, here is how to change the list of extra arguments that the Ur/Web compiler will pass to GCC on every invocation. + +\begin{verbatim} +GCCARGS=-fnested-functions ./configure +\end{verbatim} + +Some OSX users have reported needing to use this particular GCCARGS value. + +The Emacs mode can be set to autoload by adding the following to your \texttt{.emacs} file. + +\begin{verbatim} +(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/urweb-mode") +(load "urweb-mode-startup") +\end{verbatim} + +Change the path in the first line if you chose a different Emacs installation path during configuration. + + \section{Ur Syntax} In this section, we describe the syntax of Ur, deferring to a later section discussion of most of the syntax specific to SQL and XML. The sole exceptions are the declaration forms for tables, sequences, and cookies. -- cgit v1.2.3 From 5bb4dcc90dc61ef431539e049b160e2971cf4621 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 9 Dec 2008 11:52:56 -0500 Subject: .urp files --- doc/manual.tex | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 942cee77..141c4b45 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -99,6 +99,36 @@ The Emacs mode can be set to autoload by adding the following to your \texttt{.e Change the path in the first line if you chose a different Emacs installation path during configuration. +\section{Command-Line Compiler} + +\subsection{Project Files} + +The basic inputs to the \texttt{urweb} compiler are project files, which have the extension \texttt{.urp}. Here is a sample \texttt{.urp} file. + +\begin{verbatim} +database dbname=test +sql crud1.sql + +crud +crud1 +\end{verbatim} + +The \texttt{database} line gives the database information string to pass to libpq. In this case, the string only says to connect to a local database named \texttt{test}. + +The \texttt{sql} line asks for an SQL source file to be generated, giving the commands to run to create the tables and sequences that this application expects to find. After building this \texttt{.urp} file, the following commands could be used to initialize the database, assuming that the current UNIX user exists as a Postgres user with database creation privileges: + +\begin{verbatim} +createdb test +psql -f crud1.sql test +\end{verbatim} + +A blank line always separates the named directives from a list of modules to include in the project; if there are no named directives, a blank line must begin the file. + +For each entry \texttt{M} in the module list, the file \texttt{M.urs} is included in the project if it exists, and the file \texttt{M.ur} must exist and is always included. + +A few other named directives are supported. \texttt{prefix PREFIX} sets the prefix included before every URI within the generated application; the default is \texttt{/}. \texttt{exe FILENAME} sets the filename to which to write the output executable; the default for file \texttt{P.urp} is \texttt{P.exe}. \texttt{debug} saves some intermediate C files, which is mostly useful to help in debugging the compiler itself. \texttt{profile} generates an executable that may be used with gprof. + + \section{Ur Syntax} In this section, we describe the syntax of Ur, deferring to a later section discussion of most of the syntax specific to SQL and XML. The sole exceptions are the declaration forms for tables, sequences, and cookies. -- cgit v1.2.3 From 86360921e7d299c1e20c0adc5d382f70b64b822f Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 9 Dec 2008 11:57:17 -0500 Subject: Building an application --- doc/manual.tex | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 141c4b45..9255fc87 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -128,6 +128,18 @@ For each entry \texttt{M} in the module list, the file \texttt{M.urs} is include A few other named directives are supported. \texttt{prefix PREFIX} sets the prefix included before every URI within the generated application; the default is \texttt{/}. \texttt{exe FILENAME} sets the filename to which to write the output executable; the default for file \texttt{P.urp} is \texttt{P.exe}. \texttt{debug} saves some intermediate C files, which is mostly useful to help in debugging the compiler itself. \texttt{profile} generates an executable that may be used with gprof. +\subsection{Building an Application} + +To compile project \texttt{P.urp}, simply run +\begin{verbatim} +urweb P +\end{verbatim} + +To time how long the different compiler phases run, without generating an executable, run +\begin{verbatim} +urweb -timing P +\end{verbatim} + \section{Ur Syntax} -- cgit v1.2.3 From 55fefa6122803e9739e9e71f1d50eae671665df4 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 9 Dec 2008 14:06:51 -0500 Subject: Proofreading pass --- doc/manual.tex | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 9255fc87..3c97b720 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -134,6 +134,7 @@ To compile project \texttt{P.urp}, simply run \begin{verbatim} urweb P \end{verbatim} +The output executable is a standalone web server. Run it with the command-line argument \texttt{-h} to see which options it takes. If the project file lists a database, the web server will attempt to connect to that database on startup. To time how long the different compiler phases run, without generating an executable, run \begin{verbatim} @@ -188,7 +189,7 @@ $$\begin{array}{rrcll} Ur supports several different notions of functions that take types as arguments. These arguments can be either implicit, causing them to be inferred at use sites; or explicit, forcing them to be specified manually at use sites. There is a common explicitness annotation convention applied at the definitions of and in the types of such functions. $$\begin{array}{rrcll} \textrm{Explicitness} & ? &::=& :: & \textrm{explicit} \\ - &&& \; ::: & \textrm{implicit} + &&& ::: & \textrm{implicit} \end{array}$$ \emph{Constructors} are the main class of compile-time-only data. They include proper types and are classified by kinds. @@ -210,7 +211,7 @@ $$\begin{array}{rrcll} &&& c \rc c & \textrm{type-level record concatenation} \\ &&& \mt{fold} & \textrm{type-level record fold} \\ \\ - &&& (c^+) & \textrm{type-level tuple} \\ + &&& (c,^+) & \textrm{type-level tuple} \\ &&& c.n & \textrm{type-level tuple projection ($n \in \mathbb N^+$)} \\ \\ &&& \lambda [c \sim c] \Rightarrow c & \textrm{guarded constructor} \\ @@ -452,9 +453,9 @@ $$\infer{\Gamma \vdash \lambda [c_1 \sim c_2] \Rightarrow c :: \kappa}{ We will use a keyword $\mt{map}$ as a shorthand, such that, for $f$ of kind $\kappa \to \kappa'$, $\mt{map} \; f$ stands for $\mt{fold} \; (\lambda (x_1 :: \mt{Name}) (x_2 :: \kappa) (x_3 :: \{\kappa'\}) \Rightarrow [x_1 = f \; x_2] \rc x_3) \; []$. $$\infer{\Gamma \vdash c_1 \sim c_2}{ - \Gamma \vdash c_1 \hookrightarrow c'_1 - & \Gamma \vdash c_2 \hookrightarrow c'_2 - & \forall c''_1 \in c'_1, c''_2 \in c'_2: \Gamma \vdash c''_1 \sim c''_2 + \Gamma \vdash c_1 \hookrightarrow C_1 + & \Gamma \vdash c_2 \hookrightarrow C_2 + & \forall c'_1 \in C_1, c'_2 \in C_2: \Gamma \vdash c'_1 \sim c'_2 } \quad \infer{\Gamma \vdash X \sim X'}{ X \neq X' @@ -462,10 +463,10 @@ $$\infer{\Gamma \vdash c_1 \sim c_2}{ $$\infer{\Gamma \vdash c_1 \sim c_2}{ c'_1 \sim c'_2 \in \Gamma - & \Gamma \vdash c'_1 \hookrightarrow c''_1 - & \Gamma \vdash c'_2 \hookrightarrow c''_2 - & c_1 \in c''_1 - & c_2 \in c''_2 + & \Gamma \vdash c'_1 \hookrightarrow C_1 + & \Gamma \vdash c'_2 \hookrightarrow C_2 + & c_1 \in C_1 + & c_2 \in C_2 }$$ $$\infer{\Gamma \vdash c \hookrightarrow \{c\}}{} @@ -656,8 +657,7 @@ We use an auxiliary judgment $\overline{y}; x; \Gamma \vdash \overline{dc} \lead This is the first judgment where we deal with type classes, for the $\mt{class}$ declaration form. We will omit their special handling in this formal specification. Section \ref{typeclasses} gives an informal description of how type classes influence type inference. -We presuppose the existence of a function $\mathcal O$, where $\mathcal(M, \overline{s})$ implements the $\mt{open}$ declaration by producing a context with the appropriate entry for each available component of module $M$ with signature items $\overline{s}$. Where possible, $\mathcal O$ uses ``transparent'' entries (e.g., an abstract type $M.x$ is mapped to $x :: \mt{Type} = M.x$), so that the relationship with $M$ is maintained. A related function $\mathcal O_c$ builds a context containing the disjointness constraints found in $S$. - +We presuppose the existence of a function $\mathcal O$, where $\mathcal O(M, \overline{s})$ implements the $\mt{open}$ declaration by producing a context with the appropriate entry for each available component of module $M$ with signature items $\overline{s}$. Where possible, $\mathcal O$ uses ``transparent'' entries (e.g., an abstract type $M.x$ is mapped to $x :: \mt{Type} = M.x$), so that the relationship with $M$ is maintained. A related function $\mathcal O_c$ builds a context containing the disjointness constraints found in $\overline s$. We write $\kappa_1^n \to \kappa$ as a shorthand, where $\kappa_1^0 \to \kappa = \kappa$ and $\kappa_1^{n+1} \to \kappa_2 = \kappa_1 \to (\kappa_1^n \to \kappa_2)$. We write $\mt{len}(\overline{y})$ for the length of vector $\overline{y}$ of variables. $$\infer{\Gamma \vdash \cdot \leadsto \Gamma}{} @@ -690,10 +690,10 @@ $$\infer{\Gamma \vdash \mt{val} \; \mt{rec} \; \overline{x : \tau = e} \leadsto $$\infer{\Gamma \vdash \mt{structure} \; X : S = M \leadsto \Gamma, X : S}{ \Gamma \vdash M : S - & \textrm{ ($M$ not a $\mt{struct} \; \ldots \; \mt{end}$)} + & \textrm{ $M$ not a constant or application} } -\quad \infer{\Gamma \vdash \mt{structure} \; X : S = \mt{struct} \; \overline{d} \; \mt{end} \leadsto \Gamma, X : \mt{selfify}(X, \overline{s})}{ - \Gamma \vdash \mt{struct} \; \overline{d} \; \mt{end} : \mt{sig} \; \overline{s} \; \mt{end} +\quad \infer{\Gamma \vdash \mt{structure} \; X : S = M \leadsto \Gamma, X : \mt{selfify}(X, \overline{s})}{ + \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} }$$ $$\infer{\Gamma \vdash \mt{signature} \; X = S \leadsto \Gamma, X = S}{ @@ -786,7 +786,7 @@ $$\infer{\Gamma \vdash \mt{class} \; x = c \leadsto \Gamma, x :: \mt{Type} \to \ \subsection{Signature Compatibility} -To simplify the judgments in this section, we assume that all signatures are alpha-varied as necessary to avoid including mmultiple bindings for the same identifier. This is in addition to the usual alpha-variation of locally-bound variables. +To simplify the judgments in this section, we assume that all signatures are alpha-varied as necessary to avoid including multiple bindings for the same identifier. This is in addition to the usual alpha-variation of locally-bound variables. We rely on a judgment $\Gamma \vdash \overline{s} \leq s'$, which expresses the occurrence in signature items $\overline{s}$ of an item compatible with $s'$. We also use a judgment $\Gamma \vdash \overline{dc} \leq \overline{dc}$, which expresses compatibility of datatype definitions. @@ -835,7 +835,7 @@ $$\infer{\Gamma \vdash \mt{functor} (X : S_1) : S_2 \leq \mt{functor} (X : S'_1) $$\infer{\Gamma \vdash \mt{con} \; x :: \kappa \leq \mt{con} \; x :: \kappa}{} \quad \infer{\Gamma \vdash \mt{con} \; x :: \kappa = c \leq \mt{con} \; x :: \kappa}{} -\quad \infer{\Gamma \vdash \mt{datatype} \; x \; \overline{y} = \overline{dc} \leq \mt{con} \; x :: \mt{Type}}{}$$ +\quad \infer{\Gamma \vdash \mt{datatype} \; x \; \overline{y} = \overline{dc} \leq \mt{con} \; x :: \mt{Type}^{\mt{len}(\overline y)} \to \mt{Type}}{}$$ $$\infer{\Gamma \vdash \mt{datatype} \; x = \mt{datatype} \; M.z \leq \mt{con} \; x :: \mt{Type}^{\mt{len}(y)} \to \mt{Type}}{ \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} @@ -946,10 +946,9 @@ $$\infer{\Gamma \vdash M_1(M_2) : [X \mapsto M_2]S_2}{ \mt{sigOf}(\mt{cookie} \; x : \tau) &=& \mt{cookie} \; x : \tau \\ \mt{sigOf}(\mt{class} \; x = c) &=& \mt{class} \; x = c \\ \end{eqnarray*} - \begin{eqnarray*} \mt{selfify}(M, \cdot) &=& \cdot \\ - \mt{selfify}(M, s \; \overline{s'}) &=& \mt{selfify}(M, \sigma, s) \; \mt{selfify}(M, \overline{s'}) \\ + \mt{selfify}(M, s \; \overline{s'}) &=& \mt{selfify}(M, s) \; \mt{selfify}(M, \overline{s'}) \\ \\ \mt{selfify}(M, \mt{con} \; x :: \kappa) &=& \mt{con} \; x :: \kappa = M.x \\ \mt{selfify}(M, \mt{con} \; x :: \kappa = c) &=& \mt{con} \; x :: \kappa = c \\ @@ -984,7 +983,7 @@ $$\infer{\Gamma \vdash M_1(M_2) : [X \mapsto M_2]S_2}{ \mt{proj}(M, \mt{datatype} \; x = \mt{datatype} \; M'.z, \mt{val} \; X) &=& \overline{y ::: \mt{Type}} \to M.x \; \overline y \textrm{ (where $\Gamma \vdash M' : \mt{sig} \; \overline{s'} \; \mt{end}$} \\ && \textrm{and $\mt{proj}(M', \overline{s'}, \mt{datatype} \; z = (\overline{y}, \overline{dc})$ and $X \in \overline{dc}$)} \\ \mt{proj}(M, \mt{datatype} \; x = \mt{datatype} \; M'.z, \mt{val} \; X) &=& \overline{y ::: \mt{Type}} \to \tau \to M.x \; \overline y \textrm{ (where $\Gamma \vdash M' : \mt{sig} \; \overline{s'} \; \mt{end}$} \\ - && \textrm{and $\mt{proj}(M', \overline{s'}, \mt{datatype} \; z = (\overline{y}, \overline{dc})$ and $X : \tau \in \overline{dc}$)} \\ + && \textrm{and $\mt{proj}(M', \overline{s'}, \mt{datatype} \; z = (\overline{y}, \overline{dc})$ and $X \; \mt{of} \; \tau \in \overline{dc}$)} \\ \\ \mt{proj}(M, \mt{structure} \; X : S \; \overline{s}, \mt{structure} \; X) &=& S \\ \\ @@ -1391,7 +1390,7 @@ $$\begin{array}{rrcll} \textrm{Projections} & P &::=& \ast & \textrm{all columns} \\ &&& p,^+ & \textrm{particular columns} \\ \textrm{Pre-projections} & p &::=& t.f & \textrm{one column from a table} \\ - &&& t.\{\{c\}\} & \textrm{a record of colums from a table (of kind $\{\mt{Type}\}$)} \\ + &&& t.\{\{c\}\} & \textrm{a record of columns from a table (of kind $\{\mt{Type}\}$)} \\ \textrm{Table names} & t &::=& x & \textrm{constant table name (automatically capitalized)} \\ &&& X & \textrm{constant table name} \\ &&& \{\{c\}\} & \textrm{computed table name (of kind $\mt{Name}$)} \\ @@ -1462,7 +1461,7 @@ When the standalone web server receives a request for a known page, it calls the HTML forms are handled in a similar way. The $\mt{action}$ attribute of a $\mt{submit}$ form tag takes a value of type $\$\mt{use} \to \mt{transaction} \; \mt{page}$, where $\mt{use}$ is a kind-$\{\mt{Type}\}$ record of the form fields used by this action handler. Action handlers are assigned URL patterns in the same way as above. -For both links and actions, direct arguments and local variables mentioned implicitly via closures are automatically included in serialized form in URLs, in the order in which they appeared in the source code. +For both links and actions, direct arguments and local variables mentioned implicitly via closures are automatically included in serialized form in URLs, in the order in which they appear in the source code. \section{Compiler Phases} @@ -1513,11 +1512,11 @@ This phase specializes polymorphic functions to the specific arguments passed to \subsection{Specialize} -Replace uses of parametrized datatypes with versions specialized to specific parameters. As for Unpoly, this phase will not be effective enough in the presence of polymorphic recursion or other fancy uses of impredicative polymorphism. +Replace uses of parameterized datatypes with versions specialized to specific parameters. As for Unpoly, this phase will not be effective enough in the presence of polymorphic recursion or other fancy uses of impredicative polymorphism. \subsection{Shake} -Here the compiler repeats the earlier shake phase. +Here the compiler repeats the earlier Shake phase. \subsection{Monoize} -- cgit v1.2.3 From 5108a7e86734b335b65b9efd60a7f2f2797b602b Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 9 Dec 2008 14:41:19 -0500 Subject: Add SQL arithmetic operators --- doc/manual.tex | 24 +++++++++++---------- lib/basis.urs | 30 +++++++++++++++----------- lib/top.ur | 2 +- src/monoize.sml | 63 +++++++++++++++++++++++++------------------------------ src/urweb.grm | 29 +++++++++++++------------ tests/sql_ops.ur | 8 +++++++ tests/sql_ops.urp | 6 ++++++ 7 files changed, 89 insertions(+), 73 deletions(-) create mode 100644 tests/sql_ops.ur create mode 100644 tests/sql_ops.urp (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 3c97b720..21092735 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1198,7 +1198,7 @@ $$\begin{array}{l} \hspace{.1in} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; (\mt{option} \; \mt{t}) \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{bool} \end{array}$$ -We have generic nullary, unary, and binary operators, as well as comparison operators. +We have generic nullary, unary, and binary operators. $$\begin{array}{l} \mt{con} \; \mt{sql\_nfunc} :: \mt{Type} \to \mt{Type} \\ \mt{val} \; \mt{sql\_current\_timestamp} : \mt{sql\_nfunc} \; \mt{time} \\ @@ -1221,16 +1221,16 @@ $$\begin{array}{l} \end{array}$$ $$\begin{array}{l} - \mt{type} \; \mt{sql\_comparison} \\ - \mt{val} \; \mt{sql\_eq} : \mt{sql\_comparison} \\ - \mt{val} \; \mt{sql\_ne} : \mt{sql\_comparison} \\ - \mt{val} \; \mt{sql\_lt} : \mt{sql\_comparison} \\ - \mt{val} \; \mt{sql\_le} : \mt{sql\_comparison} \\ - \mt{val} \; \mt{sql\_gt} : \mt{sql\_comparison} \\ - \mt{val} \; \mt{sql\_ge} : \mt{sql\_comparison} \\ - \mt{val} \; \mt{sql\_comparison} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \\ - \hspace{.1in} \to \mt{sql\_comparison} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{t} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{t} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{bool} - \end{array}$$ + \mt{class} \; \mt{sql\_arith} \\ + \mt{val} \; \mt{sql\_int\_arith} : \mt{sql\_arith} \; \mt{int} \\ + \mt{val} \; \mt{sql\_float\_arith} : \mt{sql\_arith} \; \mt{float} \\ + \mt{val} \; \mt{sql\_neg} : \mt{t} ::: \mt{Type} \to \mt{sql\_arith} \; \mt{t} \to \mt{sql\_unary} \; \mt{t} \; \mt{t} \\ + \mt{val} \; \mt{sql\_plus} : \mt{t} ::: \mt{Type} \to \mt{sql\_arith} \; \mt{t} \to \mt{sql\_binary} \; \mt{t} \; \mt{t} \; \mt{t} \\ + \mt{val} \; \mt{sql\_minus} : \mt{t} ::: \mt{Type} \to \mt{sql\_arith} \; \mt{t} \to \mt{sql\_binary} \; \mt{t} \; \mt{t} \; \mt{t} \\ + \mt{val} \; \mt{sql\_times} : \mt{t} ::: \mt{Type} \to \mt{sql\_arith} \; \mt{t} \to \mt{sql\_binary} \; \mt{t} \; \mt{t} \; \mt{t} \\ + \mt{val} \; \mt{sql\_div} : \mt{t} ::: \mt{Type} \to \mt{sql\_arith} \; \mt{t} \to \mt{sql\_binary} \; \mt{t} \; \mt{t} \; \mt{t} \\ + \mt{val} \; \mt{sql\_mod} : \mt{sql\_binary} \; \mt{int} \; \mt{int} \; \mt{int} +\end{array}$$ Finally, we have aggregate functions. The $\mt{COUNT(\ast)}$ syntax is handled specially, since it takes no real argument. The other aggregate functions are placed into a general type family, using type classes to restrict usage to properly-typed arguments. The key aspect of the $\mt{sql\_aggregate}$ function's type is the shift of aggregate-function-only fields into unrestricted fields. @@ -1445,6 +1445,8 @@ $$\begin{array}{rrcll} \textrm{XML pieces} & l &::=& \textrm{text} & \textrm{cdata} \\ &&& \texttt{<}g\texttt{/>} & \textrm{tag with no children} \\ &&& \texttt{<}g\texttt{>}l^*\texttt{} & \textrm{tag with children} \\ + &&& \{e\} & \textrm{computed XML fragment} \\ + &&& \{[e]\} & \textrm{injection of an Ur expression, via the $\mt{Top}.\mt{txt}$ function} \\ \textrm{Tag} & g &::=& h \; (x = v)^* \\ \textrm{Tag head} & h &::=& x & \textrm{tag name} \\ &&& h\{c\} & \textrm{constructor parameter} \\ diff --git a/lib/basis.urs b/lib/basis.urs index 9681328f..eb2a6d29 100644 --- a/lib/basis.urs +++ b/lib/basis.urs @@ -202,6 +202,10 @@ val sql_is_null : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} -> sql_exp tables agg exps (option t) -> sql_exp tables agg exps bool +class sql_arith +val sql_int_arith : sql_arith int +val sql_float_arith : sql_arith float + con sql_unary :: Type -> Type -> Type val sql_not : sql_unary bool bool val sql_unary : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} @@ -209,6 +213,8 @@ val sql_unary : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {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 @@ -218,18 +224,18 @@ val sql_binary : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} -> sql_exp tables agg exps arg2 -> sql_exp tables agg exps res -type sql_comparison -val sql_eq : sql_comparison -val sql_ne : sql_comparison -val sql_lt : sql_comparison -val sql_le : sql_comparison -val sql_gt : sql_comparison -val sql_ge : sql_comparison -val sql_comparison : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} - -> t ::: Type - -> sql_comparison - -> sql_exp tables agg exps t -> sql_exp tables agg exps t - -> sql_exp tables agg exps bool +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 +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_count : tables ::: {{Type}} -> agg ::: {{Type}} -> exps ::: {Type} -> sql_exp tables agg exps int diff --git a/lib/top.ur b/lib/top.ur index 76fe73c1..fd7676a3 100644 --- a/lib/top.ur +++ b/lib/top.ur @@ -238,4 +238,4 @@ fun eqNullable' (tables ::: {{Type}}) (agg ::: {{Type}}) (exps ::: {Type}) (e2 : option t) = case e2 of None => (SQL {e1} IS NULL) - | Some _ => sql_comparison sql_eq e1 (@sql_inject inj e2) + | Some _ => sql_binary sql_eq e1 (@sql_inject inj e2) diff --git a/src/monoize.sml b/src/monoize.sml index cd20e366..1880c57d 100644 --- a/src/monoize.sml +++ b/src/monoize.sml @@ -165,14 +165,14 @@ fun monoType env = (L'.TFfi ("Basis", "string"), loc) | L.CApp ((L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_binary"), _), _), _), _), _), _) => (L'.TFfi ("Basis", "string"), loc) - | L.CFfi ("Basis", "sql_comparison") => - (L'.TFfi ("Basis", "string"), loc) | L.CApp ((L.CFfi ("Basis", "sql_aggregate"), _), t) => (L'.TFfi ("Basis", "string"), loc) | L.CApp ((L.CFfi ("Basis", "sql_summable"), _), _) => (L'.TRecord [], loc) | L.CApp ((L.CFfi ("Basis", "sql_maxable"), _), _) => (L'.TRecord [], loc) + | L.CApp ((L.CFfi ("Basis", "sql_arith"), _), _) => + (L'.TRecord [], loc) | L.CApp ((L.CFfi ("Basis", "sql_nfunc"), _), _) => (L'.TFfi ("Basis", "string"), loc) @@ -1369,19 +1369,34 @@ fun monoExp (env, st, fm) (all as (e, loc)) = fm) end - | L.EFfi ("Basis", "sql_eq") => + | L.ECApp ((L.EFfi ("Basis", "sql_eq"), _), _) => ((L'.EPrim (Prim.String "="), loc), fm) - | L.EFfi ("Basis", "sql_ne") => + | L.ECApp ((L.EFfi ("Basis", "sql_ne"), _), _) => ((L'.EPrim (Prim.String "<>"), loc), fm) - | L.EFfi ("Basis", "sql_lt") => + | L.ECApp ((L.EFfi ("Basis", "sql_lt"), _), _) => ((L'.EPrim (Prim.String "<"), loc), fm) - | L.EFfi ("Basis", "sql_le") => + | L.ECApp ((L.EFfi ("Basis", "sql_le"), _), _) => ((L'.EPrim (Prim.String "<="), loc), fm) - | L.EFfi ("Basis", "sql_gt") => + | L.ECApp ((L.EFfi ("Basis", "sql_gt"), _), _) => ((L'.EPrim (Prim.String ">"), loc), fm) - | L.EFfi ("Basis", "sql_ge") => + | L.ECApp ((L.EFfi ("Basis", "sql_ge"), _), _) => ((L'.EPrim (Prim.String ">="), loc), fm) + | L.ECApp ((L.EFfi ("Basis", "sql_plus"), _), _) => + ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc), + (L'.EPrim (Prim.String "+"), loc)), loc), fm) + | L.ECApp ((L.EFfi ("Basis", "sql_minus"), _), _) => + ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc), + (L'.EPrim (Prim.String "-"), loc)), loc), fm) + | L.ECApp ((L.EFfi ("Basis", "sql_times"), _), _) => + ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc), + (L'.EPrim (Prim.String "*"), loc)), loc), fm) + | L.ECApp ((L.EFfi ("Basis", "sql_div"), _), _) => + ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc), + (L'.EPrim (Prim.String "/"), loc)), loc), fm) + | L.EFfi ("Basis", "sql_mod") => + ((L'.EPrim (Prim.String "%"), loc), fm) + | L.ECApp ( (L.ECApp ( (L.ECApp ( @@ -1407,6 +1422,9 @@ fun monoExp (env, st, fm) (all as (e, loc)) = fm) end | L.EFfi ("Basis", "sql_not") => ((L'.EPrim (Prim.String "NOT"), loc), fm) + | L.ECApp ((L.EFfi ("Basis", "sql_neg"), _), _) => + ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc), + (L'.EPrim (Prim.String "-"), loc)), loc), fm) | L.ECApp ( (L.ECApp ( @@ -1440,32 +1458,6 @@ fun monoExp (env, st, fm) (all as (e, loc)) = | L.EFfi ("Basis", "sql_and") => ((L'.EPrim (Prim.String "AND"), loc), fm) | L.EFfi ("Basis", "sql_or") => ((L'.EPrim (Prim.String "OR"), loc), fm) - | L.ECApp ( - (L.ECApp ( - (L.ECApp ( - (L.ECApp ( - (L.EFfi ("Basis", "sql_comparison"), _), - _), _), - _), _), - _), _), - _) => - let - val s = (L'.TFfi ("Basis", "string"), loc) - fun sc s = (L'.EPrim (Prim.String s), loc) - in - ((L'.EAbs ("c", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc), - (L'.EAbs ("e1", s, (L'.TFun (s, s), loc), - (L'.EAbs ("e2", s, s, - strcat loc [sc "(", - (L'.ERel 1, loc), - sc " ", - (L'.ERel 2, loc), - sc " ", - (L'.ERel 0, loc), - sc ")"]), loc)), loc)), loc), - fm) - end - | L.ECApp ( (L.ECApp ( (L.ECApp ( @@ -1566,6 +1558,9 @@ fun monoExp (env, st, fm) (all as (e, loc)) = (L'.EPrim (Prim.String "SUM"), loc)), loc), fm) + | L.EFfi ("Basis", "sql_arith_int") => ((L'.ERecord [], loc), fm) + | L.EFfi ("Basis", "sql_arith_float") => ((L'.ERecord [], loc), fm) + | L.EFfi ("Basis", "sql_maxable_int") => ((L'.ERecord [], loc), fm) | L.EFfi ("Basis", "sql_maxable_float") => ((L'.ERecord [], loc), fm) | L.EFfi ("Basis", "sql_maxable_string") => ((L'.ERecord [], loc), fm) diff --git a/src/urweb.grm b/src/urweb.grm index 3d77905e..7798b018 100644 --- a/src/urweb.grm +++ b/src/urweb.grm @@ -119,15 +119,6 @@ fun amend_group loc (gi, tabs) = fun sql_inject (v, loc) = (EApp ((EVar (["Basis"], "sql_inject", Infer), loc), (v, loc)), loc) -fun sql_compare (oper, sqlexp1, sqlexp2, loc) = - let - val e = (EVar (["Basis"], "sql_comparison", Infer), loc) - val e = (EApp (e, (EVar (["Basis"], "sql_" ^ oper, Infer), loc)), loc) - val e = (EApp (e, sqlexp1), loc) - in - (EApp (e, sqlexp2), loc) - end - fun sql_binary (oper, sqlexp1, sqlexp2, loc) = let val e = (EVar (["Basis"], "sql_binary", Infer), loc) @@ -1239,16 +1230,24 @@ sqlexp : TRUE (sql_inject (EVar (["Basis"], "True", In | LBRACE eexp RBRACE (eexp) - | sqlexp EQ sqlexp (sql_compare ("eq", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) - | sqlexp NE sqlexp (sql_compare ("ne", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) - | sqlexp LT sqlexp (sql_compare ("lt", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) - | sqlexp LE sqlexp (sql_compare ("le", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) - | sqlexp GT sqlexp (sql_compare ("gt", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) - | sqlexp GE sqlexp (sql_compare ("ge", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + | sqlexp EQ sqlexp (sql_binary ("eq", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + | sqlexp NE sqlexp (sql_binary ("ne", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + | sqlexp LT sqlexp (sql_binary ("lt", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + | sqlexp LE sqlexp (sql_binary ("le", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + | sqlexp GT sqlexp (sql_binary ("gt", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + | sqlexp GE sqlexp (sql_binary ("ge", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + + | sqlexp PLUS sqlexp (sql_binary ("plus", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + | sqlexp MINUS sqlexp (sql_binary ("minus", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + | sqlexp STAR sqlexp (sql_binary ("times", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + | sqlexp DIVIDE sqlexp (sql_binary ("div", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + | sqlexp MOD sqlexp (sql_binary ("mod", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) | sqlexp CAND sqlexp (sql_binary ("and", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) | sqlexp OR sqlexp (sql_binary ("or", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right))) + | NOT sqlexp (sql_unary ("not", sqlexp, s (NOTleft, sqlexpright))) + | MINUS sqlexp (sql_unary ("neg", sqlexp, s (MINUSleft, sqlexpright))) | sqlexp IS NULL (let val loc = s (sqlexpleft, NULLright) diff --git a/tests/sql_ops.ur b/tests/sql_ops.ur new file mode 100644 index 00000000..34e78775 --- /dev/null +++ b/tests/sql_ops.ur @@ -0,0 +1,8 @@ +table t : { A : int, B : float } + +val q = (SELECT t.A + t.A AS X, t.B * t.B AS Y FROM t) + +fun main () : transaction page = + xml <- queryX q (fn r => {[r.X]}, {[r.Y]}
); + return {xml} + diff --git a/tests/sql_ops.urp b/tests/sql_ops.urp new file mode 100644 index 00000000..90e47b77 --- /dev/null +++ b/tests/sql_ops.urp @@ -0,0 +1,6 @@ +debug +database dbname=sql_ops +sql sql_ops.sql +exe /tmp/webapp + +sql_ops -- cgit v1.2.3 From 5d92ee7289e6df76694bebaa585160e5b3c79013 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 9 Dec 2008 14:43:43 -0500 Subject: Spell check --- doc/manual.tex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 21092735..930fd9f9 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1009,9 +1009,9 @@ The Ur/Web compiler uses \emph{heuristic type inference}, with no claims of comp \subsection{Basic Unification} -Type-checkers for languages based on the Hindly-Milner type discipline, like ML and Haskell, take advantage of \emph{principal typing} properties, making complete type inference relatively straightforward. Inference algorithms are traditionally implemented using type unification variables, at various points asserting equalities between types, in the process discovering the values of type variables. The Ur/Web compiler uses the same basic strategy, but the complexity of the type system rules out easy completeness. +Type-checkers for languages based on the Hindley-Milner type discipline, like ML and Haskell, take advantage of \emph{principal typing} properties, making complete type inference relatively straightforward. Inference algorithms are traditionally implemented using type unification variables, at various points asserting equalities between types, in the process discovering the values of type variables. The Ur/Web compiler uses the same basic strategy, but the complexity of the type system rules out easy completeness. -Type-checking can require evaluating recursive functional programs, thanks to the type-level $\mt{fold}$ operator. When a unification variable appears in such a type, the next step of computation can be undetermined. The value of that variable might be determined later, but this would be ``too late'' for the unification problems generated at the first occurrence. This is the essential source of incompletness. +Type-checking can require evaluating recursive functional programs, thanks to the type-level $\mt{fold}$ operator. When a unification variable appears in such a type, the next step of computation can be undetermined. The value of that variable might be determined later, but this would be ``too late'' for the unification problems generated at the first occurrence. This is the essential source of incompleteness. Nonetheless, the unification engine tends to do reasonably well. Unlike in ML, polymorphism is never inferred in definitions; it must be indicated explicitly by writing out constructor-level parameters. By writing these and other annotations, the programmer can generally get the type inference engine to do most of the type reconstruction work. @@ -1155,7 +1155,7 @@ $$\begin{array}{l} \mt{val} \; \mt{sql\_subset\_all} : \mt{tables} :: \{\{\mt{Type}\}\} \to \mt{sql\_subset} \; \mt{tables} \; \mt{tables} \end{array}$$ -SQL expressions are used in several places, including $\mt{SELECT}$, $\mt{WHERE}$, $\mt{HAVING}$, and $\mt{ORDER} \; \mt{BY}$ clauses. They reify a fragment of the standard SQL expression language, while making it possible to inject ``native'' Ur values in some places. The arguments to the $\mt{sql\_exp}$ type family respectively give the unrestricted-availablity table fields, the table fields that may only be used in arguments to aggregate functions, the available selected expressions, and the type of the expression. +SQL expressions are used in several places, including $\mt{SELECT}$, $\mt{WHERE}$, $\mt{HAVING}$, and $\mt{ORDER} \; \mt{BY}$ clauses. They reify a fragment of the standard SQL expression language, while making it possible to inject ``native'' Ur values in some places. The arguments to the $\mt{sql\_exp}$ type family respectively give the unrestricted-availability table fields, the table fields that may only be used in arguments to aggregate functions, the available selected expressions, and the type of the expression. $$\begin{array}{l} \mt{con} \; \mt{sql\_exp} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \to \mt{Type} \end{array}$$ -- cgit v1.2.3 From 65428eeb2cba9807043188bfddf5fbfd1bf9296b Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 20 Dec 2008 18:24:12 -0500 Subject: Typo report from megacz --- doc/manual.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 930fd9f9..af905574 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1017,7 +1017,7 @@ Nonetheless, the unification engine tends to do reasonably well. Unlike in ML, \subsection{Unifying Record Types} -The type inference engine tries to take advantage of the algebraic rules governing type-level records, as shown in Section \ref{definitional}. When two constructors of record kind are unified, they are reduce to normal forms, with like terms crossed off from each normal form until, hopefully, nothing remains. This cannot be complete, with the inclusion of unification variables. The type-checker can help you understand what goes wrong when the process fails, as it outputs the unmatched remainders of the two normal forms. +The type inference engine tries to take advantage of the algebraic rules governing type-level records, as shown in Section \ref{definitional}. When two constructors of record kind are unified, they are reduced to normal forms, with like terms crossed off from each normal form until, hopefully, nothing remains. This cannot be complete, with the inclusion of unification variables. The type-checker can help you understand what goes wrong when the process fails, as it outputs the unmatched remainders of the two normal forms. \subsection{\label{typeclasses}Type Classes} -- cgit v1.2.3 From 9030684acadec34adb8f08547dffe250ff4449d6 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Wed, 24 Dec 2008 10:48:31 -0500 Subject: More manual bug reports from megacz --- doc/manual.tex | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index af905574..0e756426 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -435,11 +435,11 @@ $$\infer{\Gamma \vdash [\overline{c_i = c'_i}] :: \{\kappa\}}{ $$\infer{\Gamma \vdash \mt{fold} :: ((\mt{Name} \to \kappa_1 \to \kappa_2 \to \kappa_2) \to \kappa_2 \to \{\kappa_1\} \to \kappa_2}{}$$ -$$\infer{\Gamma \vdash (\overline c) :: (k_1 \times \ldots \times k_n)}{ - \forall i: \Gamma \vdash c_i :: k_i +$$\infer{\Gamma \vdash (\overline c) :: (\kappa_1 \times \ldots \times \kappa_n)}{ + \forall i: \Gamma \vdash c_i :: \kappa_i } -\quad \infer{\Gamma \vdash c.i :: k_i}{ - \Gamma \vdash c :: (k_1 \times \ldots \times k_n) +\quad \infer{\Gamma \vdash c.i :: \kappa_i}{ + \Gamma \vdash c :: (\kappa_1 \times \ldots \times \kappa_n) }$$ $$\infer{\Gamma \vdash \lambda [c_1 \sim c_2] \Rightarrow c :: \kappa}{ @@ -584,6 +584,7 @@ $$\infer{\Gamma \vdash \{\overline{c = e}\} : \{\overline{c : \tau}\}}{ \quad \infer{\Gamma \vdash e_1 \rc e_2 : \$(c_1 \rc c_2)}{ \Gamma \vdash e_1 : \$c_1 & \Gamma \vdash e_2 : \$c_2 + & \Gamma \vdash c_1 \sim c_2 }$$ $$\infer{\Gamma \vdash e \rcut c : \$c'}{ @@ -609,7 +610,7 @@ $$\infer{\Gamma \vdash \mt{let} \; \overline{ed} \; \mt{in} \; e \; \mt{end} : \ & \Gamma_i \vdash e_i : \tau }$$ -$$\infer{\Gamma \vdash [c_1 \sim c_2] \Rightarrow e : [c_1 \sim c_2] \Rightarrow \tau}{ +$$\infer{\Gamma \vdash \lambda [c_1 \sim c_2] \Rightarrow e : \lambda [c_1 \sim c_2] \Rightarrow \tau}{ \Gamma \vdash c_1 :: \{\kappa\} & \Gamma \vdash c_2 :: \{\kappa\} & \Gamma, c_1 \sim c_2 \vdash e : \tau -- cgit v1.2.3 From d6e16e63172af6e1423df382e359cc9607325042 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 12 Mar 2009 10:16:59 -0400 Subject: Revising manual, through main syntax section --- doc/manual.tex | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 0e756426..fa6a113f 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -29,13 +29,14 @@ \item Return invalid HTML \item Contain dead intra-application links \item Have mismatches between HTML forms and the fields expected by their handlers +\item Include client-side code that makes incorrect assumptions about the ``AJAX''-style services that the remote web server provides \item Attempt invalid SQL queries -\item Use improper marshaling or unmarshaling in communication with SQL databases +\item Use improper marshaling or unmarshaling in communication with SQL databases or between browsers and web servers \end{itemize} This type safety is just the foundation of the Ur/Web methodology. It is also possible to use metaprogramming to build significant application pieces by analysis of type structure. For instance, the demo includes an ML-style functor for building an admin interface for an arbitrary SQL table. The type system guarantees that the admin interface sub-application that comes out will always be free of the above-listed bugs, no matter which well-typed table description is given as input. -The Ur/Web compiler also produces very efficient object code that does not use garbage collection. These compiled programs will often be even more efficient than what most programmers would bother to write in C. +The Ur/Web compiler also produces very efficient object code that does not use garbage collection. These compiled programs will often be even more efficient than what most programmers would bother to write in C. The compiler also generates JavaScript versions of client-side code, with no need to write those parts of applications in a different language. \medskip @@ -154,9 +155,11 @@ We give the Ur language definition in \LaTeX $\;$ math mode, since that is prett \begin{tabular}{rl} \textbf{\LaTeX} & \textbf{ASCII} \\ $\to$ & \cd{->} \\ + $\longrightarrow$ & \cd{-->} \\ $\times$ & \cd{*} \\ $\lambda$ & \cd{fn} \\ $\Rightarrow$ & \cd{=>} \\ + $\Longrightarrow$ & \cd{==>} \\ $\neq$ & \cd{<>} \\ $\leq$ & \cd{<=} \\ $\geq$ & \cd{>=} \\ @@ -182,6 +185,8 @@ $$\begin{array}{rrcll} &&& \kappa \to \kappa & \textrm{type-level functions} \\ &&& \{\kappa\} & \textrm{type-level records} \\ &&& (\kappa\times^+) & \textrm{type-level tuples} \\ + &&& X & \textrm{variable} \\ + &&& X \longrightarrow k & \textrm{kind-polymorphic type-level function} \\ &&& \_\_ & \textrm{wildcard} \\ &&& (\kappa) & \textrm{explicit precedence} \\ \end{array}$$ @@ -199,22 +204,25 @@ $$\begin{array}{rrcll} \\ &&& \tau \to \tau & \textrm{function type} \\ &&& x \; ? \; \kappa \to \tau & \textrm{polymorphic function type} \\ + &&& X \longrightarrow \tau & \textrm{kind-polymorphic function type} \\ &&& \$ c & \textrm{record type} \\ \\ &&& c \; c & \textrm{type-level function application} \\ &&& \lambda x \; :: \; \kappa \Rightarrow c & \textrm{type-level function abstraction} \\ \\ + &&& X \Longrightarrow c & \textrm{type-level kind-polymorphic function abstraction} \\ + \\ &&& () & \textrm{type-level unit} \\ &&& \#X & \textrm{field name} \\ \\ &&& [(c = c)^*] & \textrm{known-length type-level record} \\ &&& c \rc c & \textrm{type-level record concatenation} \\ - &&& \mt{fold} & \textrm{type-level record fold} \\ + &&& \mt{map} & \textrm{type-level record map} \\ \\ &&& (c,^+) & \textrm{type-level tuple} \\ &&& c.n & \textrm{type-level tuple projection ($n \in \mathbb N^+$)} \\ \\ - &&& \lambda [c \sim c] \Rightarrow c & \textrm{guarded constructor} \\ + &&& [c \sim c] \Rightarrow \tau & \textrm{guarded type} \\ \\ &&& \_ :: \kappa & \textrm{wildcard} \\ &&& (c) & \textrm{explicit precedence} \\ @@ -273,13 +281,13 @@ $$\begin{array}{rrcll} &&& \lambda x : \tau \Rightarrow e & \textrm{function abstraction} \\ &&& e [c] & \textrm{polymorphic function application} \\ &&& \lambda x \; ? \; \kappa \Rightarrow e & \textrm{polymorphic function abstraction} \\ + &&& X \Longrightarrow e & \textrm{kind-polymorphic function abstraction} \\ \\ &&& \{(c = e,)^*\} & \textrm{known-length record} \\ &&& e.c & \textrm{record field projection} \\ &&& e \rc e & \textrm{record concatenation} \\ &&& e \rcut c & \textrm{removal of a single record field} \\ &&& e \rcutM c & \textrm{removal of multiple record fields} \\ - &&& \mt{fold} & \textrm{fold over fields of a type-level record} \\ \\ &&& \mt{let} \; ed^* \; \mt{in} \; e \; \mt{end} & \textrm{local definitions} \\ \\ -- cgit v1.2.3 From d3248ccc1d79b3a18704fd5549371c20e3f7bada Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 12 Mar 2009 10:38:13 -0400 Subject: Revise manual, through end of Syntax --- doc/manual.tex | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index fa6a113f..d8578168 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -248,8 +248,8 @@ $$\begin{array}{rrcll} &&& \mt{signature} \; X = S & \textrm{sub-signature} \\ &&& \mt{include} \; S & \textrm{signature inclusion} \\ &&& \mt{constraint} \; c \sim c & \textrm{record disjointness constraint} \\ - &&& \mt{class} \; x & \textrm{abstract type class} \\ - &&& \mt{class} \; x = c & \textrm{concrete type class} \\ + &&& \mt{class} \; x :: \kappa & \textrm{abstract constructor class} \\ + &&& \mt{class} \; x :: \kappa = c & \textrm{concrete constructor class} \\ \\ \textrm{Datatype constructors} & dc &::=& X & \textrm{nullary constructor} \\ &&& X \; \mt{of} \; \tau & \textrm{unary constructor} \\ @@ -293,7 +293,8 @@ $$\begin{array}{rrcll} \\ &&& \mt{case} \; e \; \mt{of} \; (p \Rightarrow e|)^+ & \textrm{pattern matching} \\ \\ - &&& \lambda [c \sim c] \Rightarrow e & \textrm{guarded expression} \\ + &&& \lambda [c \sim c] \Rightarrow e & \textrm{guarded expression abstraction} \\ + &&& e \; ! & \textrm{guarded expression application} \\ \\ &&& \_ & \textrm{wildcard} \\ &&& (e) & \textrm{explicit precedence} \\ @@ -317,7 +318,7 @@ $$\begin{array}{rrcll} &&& \mt{table} \; x : c & \textrm{SQL table} \\ &&& \mt{sequence} \; x & \textrm{SQL sequence} \\ &&& \mt{cookie} \; x : \tau & \textrm{HTTP cookie} \\ - &&& \mt{class} \; x = c & \textrm{concrete type class} \\ + &&& \mt{class} \; x :: \kappa = c & \textrm{concrete constructor class} \\ \\ \textrm{Modules} & M &::=& \mt{struct} \; d^* \; \mt{end} & \textrm{constant} \\ &&& X & \textrm{variable} \\ @@ -340,17 +341,17 @@ The notation $[c_1, \ldots, c_n]$ is shorthand for $[c_1 = (), \ldots, c_n = ()] A tuple type $(\tau_1, \ldots, \tau_n)$ expands to a record type $\{1 = \tau_1, \ldots, n = \tau_n\}$, with natural numbers as field names. A tuple pattern $(p_1, \ldots, p_n)$ expands to a rigid record pattern $\{1 = p_1, \ldots, n = p_n\}$. Positive natural numbers may be used in most places where field names would be allowed. -In general, several adjacent $\lambda$ forms may be combined into one, and kind and type annotations may be omitted, in which case they are implicitly included as wildcards. More formally, for constructor-level abstractions, we can define a new non-terminal $b ::= x \mid (x :: \kappa) \mid [c \sim c]$ and allow composite abstractions of the form $\lambda b^+ \Rightarrow c$, elaborating into the obvious sequence of one core $\lambda$ per element of $b^+$. +In general, several adjacent $\lambda$ forms may be combined into one, and kind and type annotations may be omitted, in which case they are implicitly included as wildcards. More formally, for constructor-level abstractions, we can define a new non-terminal $b ::= x \mid (x :: \kappa) \mid X$ and allow composite abstractions of the form $\lambda b^+ \Rightarrow c$, elaborating into the obvious sequence of one core $\lambda$ per element of $b^+$. For any signature item or declaration that defines some entity to be equal to $A$ with classification annotation $B$ (e.g., $\mt{val} \; x : B = A$), $B$ and the preceding colon (or similar punctuation) may be omitted, in which case it is filled in as a wildcard. A signature item or declaration $\mt{type} \; x$ or $\mt{type} \; x = \tau$ is elaborated into $\mt{con} \; x :: \mt{Type}$ or $\mt{con} \; x :: \mt{Type} = \tau$, respectively. -A signature item or declaration $\mt{class} \; x = \lambda y :: \mt{Type} \Rightarrow c$ may be abbreviated $\mt{class} \; x \; y = c$. +A signature item or declaration $\mt{class} \; x = \lambda y \Rightarrow c$ may be abbreviated $\mt{class} \; x \; y = c$. -Handling of implicit and explicit constructor arguments may be tweaked with some prefixes to variable references. An expression $@x$ is a version of $x$ where all implicit constructor arguments have been made explicit. An expression $@@x$ achieves the same effect, additionally halting automatic resolution of type class instances. The same syntax works for variables projected out of modules and for capitalized variables (datatype constructors). +Handling of implicit and explicit constructor arguments may be tweaked with some prefixes to variable references. An expression $@x$ is a version of $x$ where all implicit constructor arguments have been made explicit. An expression $@@x$ achieves the same effect, additionally halting automatic resolution of type class instances and automatic proving of disjointness constraints. The default is that any prefix of a variable's type consisting only of implicit polymorphism, type class instances, and disjointness obligations is resolved automatically, with the variable treated as having the type that starts after the last implicit element, with suitable unification variables substituted. The same syntax works for variables projected out of modules and for capitalized variables (datatype constructors). -At the expression level, an analogue is available of the composite $\lambda$ form for constructors. We define the language of binders as $b ::= x \mid (x : \tau) \mid (x \; ? \; \kappa) \mid [c \sim c]$. A lone variable $x$ as a binder stands for an expression variable of unspecified type. +At the expression level, an analogue is available of the composite $\lambda$ form for constructors. We define the language of binders as $b ::= x \mid (x : \tau) \mid (x \; ? \; \kappa) \mid X \mid [c \sim c]$. A lone variable $x$ as a binder stands for an expression variable of unspecified type. A $\mt{val}$ or $\mt{val} \; \mt{rec}$ declaration may include expression binders before the equal sign, following the binder grammar from the last paragraph. Such declarations are elaborated into versions that add additional $\lambda$s to the fronts of the righthand sides, as appropriate. The keyword $\mt{fun}$ is a synonym for $\mt{val} \; \mt{rec}$. -- cgit v1.2.3 From f0ac4ec56223cd1c76a6d2b120b62373cf4f915c Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 12 Mar 2009 11:18:54 -0400 Subject: Revise manual, through static semantics --- doc/manual.tex | 126 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 84 insertions(+), 42 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index d8578168..d2a58042 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -211,6 +211,7 @@ $$\begin{array}{rrcll} &&& \lambda x \; :: \; \kappa \Rightarrow c & \textrm{type-level function abstraction} \\ \\ &&& X \Longrightarrow c & \textrm{type-level kind-polymorphic function abstraction} \\ + &&& c [\kappa] & \textrm{type-level kind-polymorphic function application} \\ \\ &&& () & \textrm{type-level unit} \\ &&& \#X & \textrm{field name} \\ @@ -231,6 +232,8 @@ $$\begin{array}{rrcll} &&& M.x & \textrm{projection from a module} \\ \end{array}$$ +We include both abstraction and application for kind polymorphism, but applications are only inferred internally; they may not be written explicitly in source programs. + Modules of the module system are described by \emph{signatures}. $$\begin{array}{rrcll} \textrm{Signatures} & S &::=& \mt{sig} \; s^* \; \mt{end} & \textrm{constant} \\ @@ -281,6 +284,7 @@ $$\begin{array}{rrcll} &&& \lambda x : \tau \Rightarrow e & \textrm{function abstraction} \\ &&& e [c] & \textrm{polymorphic function application} \\ &&& \lambda x \; ? \; \kappa \Rightarrow e & \textrm{polymorphic function abstraction} \\ + &&& e [\kappa] & \textrm{kind-polymorphic function application} \\ &&& X \Longrightarrow e & \textrm{kind-polymorphic function abstraction} \\ \\ &&& \{(c = e,)^*\} & \textrm{known-length record} \\ @@ -303,6 +307,8 @@ $$\begin{array}{rrcll} &&& \cd{val} \; \cd{rec} \; (x : \tau = e \; \cd{and})^+ & \textrm{mutually-recursive values} \\ \end{array}$$ +As with constructors, we include both abstraction and application for kind polymorphism, but applications are only inferred internally. + \emph{Declarations} primarily bring new symbols into context. $$\begin{array}{rrcll} \textrm{Declarations} & d &::=& \mt{con} \; x :: \kappa = c & \textrm{constructor synonym} \\ @@ -374,6 +380,7 @@ In this section, we give a declarative presentation of Ur's typing rules and rel Since there is significant mutual recursion among the judgments, we introduce them all before beginning to give rules. We use the same variety of contexts throughout this section, implicitly introducing new sorts of context entries as needed. \begin{itemize} +\item $\Gamma \vdash \kappa$ expresses kind well-formedness. \item $\Gamma \vdash c :: \kappa$ assigns a kind to a constructor in a context. \item $\Gamma \vdash c \sim c$ proves the disjointness of two record constructors; that is, that they share no field names. We overload the judgment to apply to pairs of field names as well. \item $\Gamma \vdash c \hookrightarrow C$ proves that record constructor $c$ decomposes into set $C$ of field names and record constructors. @@ -388,8 +395,34 @@ Since there is significant mutual recursion among the judgments, we introduce th \item $\mt{selfify}(M, \overline{s})$ adds information to signature items $\overline{s}$ to reflect the fact that we are concerned with the particular module $M$. This function is overloaded to work over individual signature items as well. \end{itemize} + +\subsection{Kind Well-Formedness} + +$$\infer{\Gamma \vdash \mt{Type}}{} +\quad \infer{\Gamma \vdash \mt{Unit}}{} +\quad \infer{\Gamma \vdash \mt{Name}}{} +\quad \infer{\Gamma \vdash \kappa_1 \to \kappa_2}{ + \Gamma \vdash \kappa_1 + & \Gamma \vdash \kappa_2 +} +\quad \infer{\Gamma \vdash \{\kappa\}}{ + \Gamma \vdash \kappa +} +\quad \infer{\Gamma \vdash (\kappa_1 \times \ldots \times \kappa_n)}{ + \forall i: \Gamma \vdash \kappa_i +}$$ + +$$\infer{\Gamma \vdash X}{ + X \in \Gamma +} +\quad \infer{\Gamma \vdash X \longrightarrow \kappa}{ + \Gamma, X \vdash \kappa +}$$ + \subsection{Kinding} +We write $[X \mapsto \kappa_1]\kappa_2$ for capture-avoiding substitution of $\kappa_1$ for $X$ in $\kappa_2$. + $$\infer{\Gamma \vdash (c) :: \kappa :: \kappa}{ \Gamma \vdash c :: \kappa } @@ -416,6 +449,9 @@ $$\infer{\Gamma \vdash \tau_1 \to \tau_2 :: \mt{Type}}{ \quad \infer{\Gamma \vdash x \; ? \: \kappa \to \tau :: \mt{Type}}{ \Gamma, x :: \kappa \vdash \tau :: \mt{Type} } +\quad \infer{\Gamma \vdash X \longrightarrow \tau :: \mt{Type}}{ + \Gamma, X \vdash \tau :: \mt{Type} +} \quad \infer{\Gamma \vdash \$c :: \mt{Type}}{ \Gamma \vdash c :: \{\mt{Type}\} }$$ @@ -428,6 +464,14 @@ $$\infer{\Gamma \vdash c_1 \; c_2 :: \kappa_2}{ \Gamma, x :: \kappa_1 \vdash c :: \kappa_2 }$$ +$$\infer{\Gamma \vdash c[\kappa'] :: [X \mapsto \kappa']\kappa}{ + \Gamma \vdash c :: X \to \kappa + & \Gamma \vdash \kappa' +} +\quad \infer{\Gamma \vdash X \Longrightarrow c :: X \to \kappa}{ + \Gamma, X \vdash c :: \kappa +}$$ + $$\infer{\Gamma \vdash () :: \mt{Unit}}{} \quad \infer{\Gamma \vdash \#X :: \mt{Name}}{}$$ @@ -442,7 +486,7 @@ $$\infer{\Gamma \vdash [\overline{c_i = c'_i}] :: \{\kappa\}}{ & \Gamma \vdash c_1 \sim c_2 }$$ -$$\infer{\Gamma \vdash \mt{fold} :: ((\mt{Name} \to \kappa_1 \to \kappa_2 \to \kappa_2) \to \kappa_2 \to \{\kappa_1\} \to \kappa_2}{}$$ +$$\infer{\Gamma \vdash \mt{map} :: (\kappa_1 \to \kappa_2) \to \{\kappa_1\} \to \{\kappa_2\}}{}$$ $$\infer{\Gamma \vdash (\overline c) :: (\kappa_1 \times \ldots \times \kappa_n)}{ \forall i: \Gamma \vdash c_i :: \kappa_i @@ -451,16 +495,14 @@ $$\infer{\Gamma \vdash (\overline c) :: (\kappa_1 \times \ldots \times \kappa_n) \Gamma \vdash c :: (\kappa_1 \times \ldots \times \kappa_n) }$$ -$$\infer{\Gamma \vdash \lambda [c_1 \sim c_2] \Rightarrow c :: \kappa}{ - \Gamma \vdash c_1 :: \{\kappa'\} +$$\infer{\Gamma \vdash \lambda [c_1 \sim c_2] \Rightarrow \tau :: \mt{Type}}{ + \Gamma \vdash c_1 :: \{\kappa\} & \Gamma \vdash c_2 :: \{\kappa'\} - & \Gamma, c_1 \sim c_2 \vdash c :: \kappa + & \Gamma, c_1 \sim c_2 \vdash \tau :: \mt{Type} }$$ \subsection{Record Disjointness} -We will use a keyword $\mt{map}$ as a shorthand, such that, for $f$ of kind $\kappa \to \kappa'$, $\mt{map} \; f$ stands for $\mt{fold} \; (\lambda (x_1 :: \mt{Name}) (x_2 :: \kappa) (x_3 :: \{\kappa'\}) \Rightarrow [x_1 = f \; x_2] \rc x_3) \; []$. - $$\infer{\Gamma \vdash c_1 \sim c_2}{ \Gamma \vdash c_1 \hookrightarrow C_1 & \Gamma \vdash c_2 \hookrightarrow C_2 @@ -494,7 +536,7 @@ $$\infer{\Gamma \vdash c \hookrightarrow \{c\}}{} \subsection{\label{definitional}Definitional Equality} -We use $\mathcal C$ to stand for a one-hole context that, when filled, yields a constructor. The notation $\mathcal C[c]$ plugs $c$ into $\mathcal C$. We omit the standard definition of one-hole contexts. We write $[x \mapsto c_1]c_2$ for capture-avoiding substitution of $c_1$ for $x$ in $c_2$. +We use $\mathcal C$ to stand for a one-hole context that, when filled, yields a constructor. The notation $\mathcal C[c]$ plugs $c$ into $\mathcal C$. We omit the standard definition of one-hole contexts. We write $[x \mapsto c_1]c_2$ for capture-avoiding substitution of $c_1$ for $x$ in $c_2$, with analogous notation for substituting a kind in a constructor. $$\infer{\Gamma \vdash c \equiv c}{} \quad \infer{\Gamma \vdash c_1 \equiv c_2}{ @@ -518,21 +560,20 @@ $$\infer{\Gamma \vdash x \equiv c}{ \quad \infer{\Gamma \vdash (\overline c).i \equiv c_i}{}$$ $$\infer{\Gamma \vdash (\lambda x :: \kappa \Rightarrow c) \; c' \equiv [x \mapsto c'] c}{} -\quad \infer{\Gamma \vdash c_1 \rc c_2 \equiv c_2 \rc c_1}{} +\quad \infer{\Gamma \vdash (X \Longrightarrow c) [\kappa] \equiv [X \mapsto \kappa] c}{}$$ + +$$\infer{\Gamma \vdash c_1 \rc c_2 \equiv c_2 \rc c_1}{} \quad \infer{\Gamma \vdash c_1 \rc (c_2 \rc c_3) \equiv (c_1 \rc c_2) \rc c_3}{}$$ $$\infer{\Gamma \vdash [] \rc c \equiv c}{} \quad \infer{\Gamma \vdash [\overline{c_1 = c'_1}] \rc [\overline{c_2 = c'_2}] \equiv [\overline{c_1 = c'_1}, \overline{c_2 = c'_2}]}{}$$ -$$\infer{\Gamma \vdash \lambda [c_1 \sim c_2] \Rightarrow c \equiv c}{ - \Gamma \vdash c_1 \sim c_2 -} -\quad \infer{\Gamma \vdash \mt{fold} \; f \; i \; [] \equiv i}{} -\quad \infer{\Gamma \vdash \mt{fold} \; f \; i \; ([c_1 = c_2] \rc c) \equiv f \; c_1 \; c_2 \; (\mt{fold} \; f \; i \; c)}{}$$ +$$\infer{\Gamma \vdash \mt{map} \; f \; [] \equiv []}{} +\quad \infer{\Gamma \vdash \mt{map} \; f \; ([c_1 = c_2] \rc c) \equiv [c_1 = f \; c_2] \rc \mt{map} \; f \; c}{}$$ $$\infer{\Gamma \vdash \mt{map} \; (\lambda x \Rightarrow x) \; c \equiv c}{} -\quad \infer{\Gamma \vdash \mt{fold} \; f \; i \; (\mt{map} \; f' \; c) - \equiv \mt{fold} \; (\lambda (x_1 :: \mt{Name}) (x_2 :: \kappa) \Rightarrow f \; x_1 \; (f' \; x_2)) \; i \; c}{}$$ +\quad \infer{\Gamma \vdash \mt{map} \; f \; (\mt{map} \; f' \; c) + \equiv \mt{map} \; (\lambda x \Rightarrow f \; (f' \; x)) \; c}{}$$ $$\infer{\Gamma \vdash \mt{map} \; f \; (c_1 \rc c_2) \equiv \mt{map} \; f \; c_1 \rc \mt{map} \; f \; c_2}{}$$ @@ -582,6 +623,14 @@ $$\infer{\Gamma \vdash e [c] : [x \mapsto c]\tau}{ \Gamma, x :: \kappa \vdash e : \tau }$$ +$$\infer{\Gamma \vdash e [\kappa] : [X \mapsto \kappa]\tau}{ + \Gamma \vdash e : X \longrightarrow \tau + & \Gamma \vdash \kappa +} +\quad \infer{\Gamma \vdash X \Longrightarrow e : X \longrightarrow \tau}{ + \Gamma, X \vdash e : \tau +}$$ + $$\infer{\Gamma \vdash \{\overline{c = e}\} : \{\overline{c : \tau}\}}{ \forall i: \Gamma \vdash c_i :: \mt{Name} & \Gamma \vdash e_i : \tau_i @@ -603,13 +652,6 @@ $$\infer{\Gamma \vdash e \rcut c : \$c'}{ \Gamma \vdash e : \$(c \rc c') }$$ -$$\infer{\Gamma \vdash \mt{fold} : \begin{array}{c} - x_1 :: (\{\kappa\} \to \tau) - \to (x_2 :: \mt{Name} \to x_3 :: \kappa \to x_4 :: \{\kappa\} \to \lambda [[x_2] \sim x_4] - \Rightarrow x_1 \; x_4 \to x_1 \; ([x_2 = x_3] \rc x_4)) \\ - \to x_1 \; [] \to x_5 :: \{\kappa\} \to x_1 \; x_5 - \end{array}}{}$$ - $$\infer{\Gamma \vdash \mt{let} \; \overline{ed} \; \mt{in} \; e \; \mt{end} : \tau}{ \Gamma \vdash \overline{ed} \leadsto \Gamma' & \Gamma' \vdash e : \tau @@ -621,7 +663,7 @@ $$\infer{\Gamma \vdash \mt{let} \; \overline{ed} \; \mt{in} \; e \; \mt{end} : \ $$\infer{\Gamma \vdash \lambda [c_1 \sim c_2] \Rightarrow e : \lambda [c_1 \sim c_2] \Rightarrow \tau}{ \Gamma \vdash c_1 :: \{\kappa\} - & \Gamma \vdash c_2 :: \{\kappa\} + & \Gamma \vdash c_2 :: \{\kappa'\} & \Gamma, c_1 \sim c_2 \vdash e : \tau }$$ @@ -665,7 +707,7 @@ $$\infer{\Gamma \vdash \{\overline{x = p}\} \leadsto \Gamma_n; \{\overline{x = \ We use an auxiliary judgment $\overline{y}; x; \Gamma \vdash \overline{dc} \leadsto \Gamma'$, expressing the enrichment of $\Gamma$ with the types of the datatype constructors $\overline{dc}$, when they are known to belong to datatype $x$ with type parameters $\overline{y}$. -This is the first judgment where we deal with type classes, for the $\mt{class}$ declaration form. We will omit their special handling in this formal specification. Section \ref{typeclasses} gives an informal description of how type classes influence type inference. +This is the first judgment where we deal with constructor classes, for the $\mt{class}$ declaration form. We will omit their special handling in this formal specification. Section \ref{typeclasses} gives an informal description of how constructor classes influence type inference. We presuppose the existence of a function $\mathcal O$, where $\mathcal O(M, \overline{s})$ implements the $\mt{open}$ declaration by producing a context with the appropriate entry for each available component of module $M$ with signature items $\overline{s}$. Where possible, $\mathcal O$ uses ``transparent'' entries (e.g., an abstract type $M.x$ is mapped to $x :: \mt{Type} = M.x$), so that the relationship with $M$ is maintained. A related function $\mathcal O_c$ builds a context containing the disjointness constraints found in $\overline s$. We write $\kappa_1^n \to \kappa$ as a shorthand, where $\kappa_1^0 \to \kappa = \kappa$ and $\kappa_1^{n+1} \to \kappa_2 = \kappa_1 \to (\kappa_1^n \to \kappa_2)$. We write $\mt{len}(\overline{y})$ for the length of vector $\overline{y}$ of variables. @@ -732,8 +774,8 @@ $$\infer{\Gamma \vdash \mt{cookie} \; x : \tau \leadsto \Gamma, x : \mt{Basis}.\ \Gamma \vdash \tau :: \mt{Type} }$$ -$$\infer{\Gamma \vdash \mt{class} \; x = c \leadsto \Gamma, x :: \mt{Type} \to \mt{Type} = c}{ - \Gamma \vdash c :: \mt{Type} \to \mt{Type} +$$\infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leadsto \Gamma, x :: \kappa \to \mt{Type} = c}{ + \Gamma \vdash c :: \kappa \to \mt{Type} }$$ $$\infer{\overline{y}; x; \Gamma \vdash \cdot \leadsto \Gamma}{} @@ -789,10 +831,10 @@ $$\infer{\Gamma \vdash \mt{constraint} \; c_1 \sim c_2 \leadsto \Gamma, c_1 \sim & \Gamma \vdash c_2 :: \{\kappa\} }$$ -$$\infer{\Gamma \vdash \mt{class} \; x = c \leadsto \Gamma, x :: \mt{Type} \to \mt{Type} = c}{ - \Gamma \vdash c :: \mt{Type} \to \mt{Type} +$$\infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leadsto \Gamma, x :: \kappa \to \mt{Type} = c}{ + \Gamma \vdash c :: \kappa \to \mt{Type} } -\quad \infer{\Gamma \vdash \mt{class} \; x \leadsto \Gamma, x :: \mt{Type} \to \mt{Type}}{}$$ +\quad \infer{\Gamma \vdash \mt{class} \; x :: \kappa \leadsto \Gamma, x :: \kappa \to \mt{Type}}{}$$ \subsection{Signature Compatibility} @@ -852,13 +894,13 @@ $$\infer{\Gamma \vdash \mt{datatype} \; x = \mt{datatype} \; M.z \leq \mt{con} \ & \mt{proj}(M, \overline{s}, \mt{datatype} \; z) = (\overline{y}, \overline{dc}) }$$ -$$\infer{\Gamma \vdash \mt{class} \; x \leq \mt{con} \; x :: \mt{Type} \to \mt{Type}}{} -\quad \infer{\Gamma \vdash \mt{class} \; x = c \leq \mt{con} \; x :: \mt{Type} \to \mt{Type}}{}$$ +$$\infer{\Gamma \vdash \mt{class} \; x :: \kappa \leq \mt{con} \; x :: \kappa \to \mt{Type}}{} +\quad \infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leq \mt{con} \; x :: \kappa \to \mt{Type}}{}$$ $$\infer{\Gamma \vdash \mt{con} \; x :: \kappa = c_1 \leq \mt{con} \; x :: \mt{\kappa} = c_2}{ \Gamma \vdash c_1 \equiv c_2 } -\quad \infer{\Gamma \vdash \mt{class} \; x = c_1 \leq \mt{con} \; x :: \mt{Type} \to \mt{Type} = c_2}{ +\quad \infer{\Gamma \vdash \mt{class} \; x :: \kappa = c_1 \leq \mt{con} \; x :: \kappa \to \mt{Type} = c_2}{ \Gamma \vdash c_1 \equiv c_2 }$$ @@ -901,9 +943,9 @@ $$\infer{\Gamma \vdash \mt{constraint} \; c_1 \sim c_2 \leq \mt{constraint} \; c & \Gamma \vdash c_2 \equiv c'_2 }$$ -$$\infer{\Gamma \vdash \mt{class} \; x \leq \mt{class} \; x}{} -\quad \infer{\Gamma \vdash \mt{class} \; x = c \leq \mt{class} \; x}{} -\quad \infer{\Gamma \vdash \mt{class} \; x = c_1 \leq \mt{class} \; x = c_2}{ +$$\infer{\Gamma \vdash \mt{class} \; x :: \kappa \leq \mt{class} \; x :: \kappa}{} +\quad \infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leq \mt{class} \; x :: \kappa}{} +\quad \infer{\Gamma \vdash \mt{class} \; x :: \kappa = c_1 \leq \mt{class} \; x :: \kappa = c_2}{ \Gamma \vdash c_1 \equiv c_2 }$$ @@ -954,7 +996,7 @@ $$\infer{\Gamma \vdash M_1(M_2) : [X \mapsto M_2]S_2}{ \mt{sigOf}(\mt{table} \; x : c) &=& \mt{table} \; x : c \\ \mt{sigOf}(\mt{sequence} \; x) &=& \mt{sequence} \; x \\ \mt{sigOf}(\mt{cookie} \; x : \tau) &=& \mt{cookie} \; x : \tau \\ - \mt{sigOf}(\mt{class} \; x = c) &=& \mt{class} \; x = c \\ + \mt{sigOf}(\mt{class} \; x :: \kappa = c) &=& \mt{class} \; x :: \kappa = c \\ \end{eqnarray*} \begin{eqnarray*} \mt{selfify}(M, \cdot) &=& \cdot \\ @@ -969,8 +1011,8 @@ $$\infer{\Gamma \vdash M_1(M_2) : [X \mapsto M_2]S_2}{ \mt{selfify}(M, \mt{signature} \; X = S) &=& \mt{signature} \; X = S \\ \mt{selfify}(M, \mt{include} \; S) &=& \mt{include} \; S \\ \mt{selfify}(M, \mt{constraint} \; c_1 \sim c_2) &=& \mt{constraint} \; c_1 \sim c_2 \\ - \mt{selfify}(M, \mt{class} \; x) &=& \mt{class} \; x = M.x \\ - \mt{selfify}(M, \mt{class} \; x = c) &=& \mt{class} \; x = c \\ + \mt{selfify}(M, \mt{class} \; x :: \kappa) &=& \mt{class} \; x :: \kappa = M.x \\ + \mt{selfify}(M, \mt{class} \; x :: \kappa = c) &=& \mt{class} \; x :: \kappa = c \\ \end{eqnarray*} \subsection{Module Projection} @@ -981,8 +1023,8 @@ $$\infer{\Gamma \vdash M_1(M_2) : [X \mapsto M_2]S_2}{ \mt{proj}(M, \mt{datatype} \; x \; \overline{y} = \overline{dc} \; \overline{s}, \mt{con} \; x) &=& \mt{Type}^{\mt{len}(\overline{y})} \to \mt{Type} \\ \mt{proj}(M, \mt{datatype} \; x = \mt{datatype} \; M'.z \; \overline{s}, \mt{con} \; x) &=& (\mt{Type}^{\mt{len}(\overline{y})} \to \mt{Type}, M'.z) \textrm{ (where $\Gamma \vdash M' : \mt{sig} \; \overline{s'} \; \mt{end}$} \\ && \textrm{and $\mt{proj}(M', \overline{s'}, \mt{datatype} \; z) = (\overline{y}, \overline{dc})$)} \\ - \mt{proj}(M, \mt{class} \; x \; \overline{s}, \mt{con} \; x) &=& \mt{Type} \to \mt{Type} \\ - \mt{proj}(M, \mt{class} \; x = c \; \overline{s}, \mt{con} \; x) &=& (\mt{Type} \to \mt{Type}, c) \\ + \mt{proj}(M, \mt{class} \; x :: \kappa \; \overline{s}, \mt{con} \; x) &=& \kappa \to \mt{Type} \\ + \mt{proj}(M, \mt{class} \; x :: \kappa = c \; \overline{s}, \mt{con} \; x) &=& (\kappa \to \mt{Type}, c) \\ \\ \mt{proj}(M, \mt{datatype} \; x \; \overline{y} = \overline{dc} \; \overline{s}, \mt{datatype} \; x) &=& (\overline{y}, \overline{dc}) \\ \mt{proj}(M, \mt{datatype} \; x = \mt{datatype} \; M'.z \; \overline{s}, \mt{con} \; x) &=& \mt{proj}(M', \overline{s'}, \mt{datatype} \; z) \textrm{ (where $\Gamma \vdash M' : \mt{sig} \; \overline{s'} \; \mt{end}$)} \\ @@ -1008,8 +1050,8 @@ $$\infer{\Gamma \vdash M_1(M_2) : [X \mapsto M_2]S_2}{ \mt{proj}(M, \mt{signature} \; X = S \; \overline{s}, V) &=& [X \mapsto M.X]\mt{proj}(M, \overline{s}, V) \\ \mt{proj}(M, \mt{include} \; S \; \overline{s}, V) &=& \mt{proj}(M, \overline{s'} \; \overline{s}, V) \textrm{ (where $\Gamma \vdash S \equiv \mt{sig} \; \overline{s'} \; \mt{end}$)} \\ \mt{proj}(M, \mt{constraint} \; c_1 \sim c_2 \; \overline{s}, V) &=& \mt{proj}(M, \overline{s}, V) \\ - \mt{proj}(M, \mt{class} \; x \; \overline{s}, V) &=& [x \mapsto M.x]\mt{proj}(M, \overline{s}, V) \\ - \mt{proj}(M, \mt{class} \; x = c \; \overline{s}, V) &=& [x \mapsto M.x]\mt{proj}(M, \overline{s}, V) \\ + \mt{proj}(M, \mt{class} \; x :: \kappa \; \overline{s}, V) &=& [x \mapsto M.x]\mt{proj}(M, \overline{s}, V) \\ + \mt{proj}(M, \mt{class} \; x :: \kappa = c \; \overline{s}, V) &=& [x \mapsto M.x]\mt{proj}(M, \overline{s}, V) \\ \end{eqnarray*} -- cgit v1.2.3 From b2fb4c78b1713f2de24c7f476d462fcca9a27ddb Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 12 Mar 2009 11:27:23 -0400 Subject: Revise type inference section --- doc/manual.tex | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index d2a58042..b7925194 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1063,7 +1063,7 @@ The Ur/Web compiler uses \emph{heuristic type inference}, with no claims of comp Type-checkers for languages based on the Hindley-Milner type discipline, like ML and Haskell, take advantage of \emph{principal typing} properties, making complete type inference relatively straightforward. Inference algorithms are traditionally implemented using type unification variables, at various points asserting equalities between types, in the process discovering the values of type variables. The Ur/Web compiler uses the same basic strategy, but the complexity of the type system rules out easy completeness. -Type-checking can require evaluating recursive functional programs, thanks to the type-level $\mt{fold}$ operator. When a unification variable appears in such a type, the next step of computation can be undetermined. The value of that variable might be determined later, but this would be ``too late'' for the unification problems generated at the first occurrence. This is the essential source of incompleteness. +Type-checking can require evaluating recursive functional programs, thanks to the type-level $\mt{map}$ operator. When a unification variable appears in such a type, the next step of computation can be undetermined. The value of that variable might be determined later, but this would be ``too late'' for the unification problems generated at the first occurrence. This is the essential source of incompleteness. Nonetheless, the unification engine tends to do reasonably well. Unlike in ML, polymorphism is never inferred in definitions; it must be indicated explicitly by writing out constructor-level parameters. By writing these and other annotations, the programmer can generally get the type inference engine to do most of the type reconstruction work. @@ -1071,23 +1071,23 @@ Nonetheless, the unification engine tends to do reasonably well. Unlike in ML, The type inference engine tries to take advantage of the algebraic rules governing type-level records, as shown in Section \ref{definitional}. When two constructors of record kind are unified, they are reduced to normal forms, with like terms crossed off from each normal form until, hopefully, nothing remains. This cannot be complete, with the inclusion of unification variables. The type-checker can help you understand what goes wrong when the process fails, as it outputs the unmatched remainders of the two normal forms. -\subsection{\label{typeclasses}Type Classes} +\subsection{\label{typeclasses}Constructor Classes} -Ur includes a type class facility inspired by Haskell's. The current version is very rudimentary, only supporting instances for particular types built up from abstract types and datatypes and type-level application. +Ur includes a constructor class facility inspired by Haskell's. The current version is very rudimentary, only supporting instances for particular constructors built up from abstract constructors and datatypes and type-level application. -Type classes are integrated with the module system. A type class is just a constructor of kind $\mt{Type} \to \mt{Type}$. By marking such a constructor $c$ as a type class, the programmer instructs the type inference engine to, in each scope, record all values of types $c \; \tau$ as \emph{instances}. Any function argument whose type is of such a form is treated as implicit, to be determined by examining the current instance database. +Constructor classes are integrated with the module system. A constructor class of kind $\kappa$ is just a constructor of kind $\kappa \to \mt{Type}$. By marking such a constructor $c$ as a constructor class, the programmer instructs the type inference engine to, in each scope, record all values of types $c \; c'$ as \emph{instances}. Any function argument whose type is of such a form is treated as implicit, to be determined by examining the current instance database. -The ``dictionary encoding'' often used in Haskell implementations is made explicit in Ur. Type class instances are just properly-typed values, and they can also be considered as ``proofs'' of membership in the class. In some cases, it is useful to pass these proofs around explicitly. An underscore written where a proof is expected will also be inferred, if possible, from the current instance database. +The ``dictionary encoding'' often used in Haskell implementations is made explicit in Ur. Constructor class instances are just properly-typed values, and they can also be considered as ``proofs'' of membership in the class. In some cases, it is useful to pass these proofs around explicitly. An underscore written where a proof is expected will also be inferred, if possible, from the current instance database. -Just as for types, type classes may be exported from modules, and they may be exported as concrete or abstract. Concrete type classes have their ``real'' definitions exposed, so that client code may add new instances freely. Abstract type classes are useful as ``predicates'' that can be used to enforce invariants, as we will see in some definitions of SQL syntax in the Ur/Web standard library. +Just as for constructors, constructors classes may be exported from modules, and they may be exported as concrete or abstract. Concrete constructor classes have their ``real'' definitions exposed, so that client code may add new instances freely. Abstract constructor classes are useful as ``predicates'' that can be used to enforce invariants, as we will see in some definitions of SQL syntax in the Ur/Web standard library. \subsection{Reverse-Engineering Record Types} -It's useful to write Ur functions and functors that take record constructors as inputs, but these constructors can grow quite long, even though their values are often implied by other arguments. The compiler uses a simple heuristic to infer the values of unification variables that are folded over, yielding known results. Often, as in the case of $\mt{map}$-like folds, the base and recursive cases of a fold produce constructors with different top-level structure. Thus, if the result of the fold is known, examining its top-level structure reveals whether the record being folded over is empty or not. If it's empty, we're done; if it's not empty, we replace a single unification variable with a new constructor formed from three new unification variables, as in $[\alpha = \beta] \rc \gamma$. This process can often be repeated to determine a unification variable fully. +It's useful to write Ur functions and functors that take record constructors as inputs, but these constructors can grow quite long, even though their values are often implied by other arguments. The compiler uses a simple heuristic to infer the values of unification variables that are mapped over, yielding known results. If the result is empty, we're done; if it's not empty, we replace a single unification variable with a new constructor formed from three new unification variables, as in $[\alpha = \beta] \rc \gamma$. This process can often be repeated to determine a unification variable fully. \subsection{Implicit Arguments in Functor Applications} -Constructor, constraint, and type class witness members of structures may be omitted, when those structures are used in contexts where their assigned signatures imply how to fill in those missing members. This feature combines well with reverse-engineering to allow for uses of complicated meta-programming functors with little more code than would be necessary to invoke an untyped, ad-hoc code generator. +Constructor, constraint, and constructor class witness members of structures may be omitted, when those structures are used in contexts where their assigned signatures imply how to fill in those missing members. This feature combines well with reverse-engineering to allow for uses of complicated meta-programming functors with little more code than would be necessary to invoke an untyped, ad-hoc code generator. \section{The Ur Standard Library} @@ -1284,7 +1284,7 @@ $$\begin{array}{l} \mt{val} \; \mt{sql\_mod} : \mt{sql\_binary} \; \mt{int} \; \mt{int} \; \mt{int} \end{array}$$ -Finally, we have aggregate functions. The $\mt{COUNT(\ast)}$ syntax is handled specially, since it takes no real argument. The other aggregate functions are placed into a general type family, using type classes to restrict usage to properly-typed arguments. The key aspect of the $\mt{sql\_aggregate}$ function's type is the shift of aggregate-function-only fields into unrestricted fields. +Finally, we have aggregate functions. The $\mt{COUNT(\ast)}$ syntax is handled specially, since it takes no real argument. The other aggregate functions are placed into a general type family, using constructor classes to restrict usage to properly-typed arguments. The key aspect of the $\mt{sql\_aggregate}$ function's type is the shift of aggregate-function-only fields into unrestricted fields. $$\begin{array}{l} \mt{val} \; \mt{sql\_count} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{int} -- cgit v1.2.3 From daeb6356944c37fceb2325ab28e696200d7d0988 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 12 Mar 2009 11:36:27 -0400 Subject: Describe folders --- doc/manual.tex | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index b7925194..7a0a0002 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1110,6 +1110,22 @@ $$\begin{array}{l} \mt{datatype} \; \mt{option} \; \mt{t} = \mt{None} \mid \mt{Some} \; \mt{of} \; \mt{t} \end{array}$$ +Another important generic Ur element comes at the beginning of \texttt{top.urs}. + +$$\begin{array}{l} + \mt{con} \; \mt{folder} :: \mt{K} \longrightarrow \{\mt{K}\} \to \mt{Type} \\ + \\ + \mt{val} \; \mt{fold} : \mt{K} \longrightarrow \mt{tf} :: (\{\mt{K}\} \to \mt{Type}) \\ + \hspace{.1in} \to (\mt{nm} :: \mt{Name} \to \mt{v} :: \mt{K} \to \mt{r} :: \{\mt{K}\} \to [[\mt{nm}] \sim \mt{r}] \Rightarrow \\ + \hspace{.2in} \mt{tf} \; \mt{r} \to \mt{tf} \; ([\mt{nm} = \mt{v}] \rc \mt{r})) \\ + \hspace{.1in} \to \mt{tf} \; [] \\ + \hspace{.1in} \to \mt{r} :: \{\mt{K}\} \to \mt{folder} \; \mt{r} \to \mt{tf} \; \mt{r} +\end{array}$$ + +For a type-level record $\mt{r}$, a $\mt{folder} \; \mt{r}$ encodes a permutation of $\mt{r}$'s elements. The $\mt{fold}$ function can be called on a $\mt{folder}$ to iterate over the elements of $\mt{r}$ in that order. $\mt{fold}$ is parameterized on a type-level function to be used to calculate the type of each intermediate result of folding. After processing a subset $\mt{r'}$ of $\mt{r}$'s entries, the type of the accumulator should be $\mt{tf} \; \mt{r'}$. The next two expression arguments to $\mt{fold}$ are the usual step function and initial accumulator, familiar from fold functions over lists. The final two arguments are the record to fold over and a $\mt{folder}$ for it. + +The Ur compiler treates $\mt{folder}$ like a constructor class, using built-in rules to infer $\mt{folder}$s for records with known structure. The order in which field names are mentioned in source code is used as a hint about the permutation that the programmer would like. + \section{The Ur/Web Standard Library} -- cgit v1.2.3 From a7ec41ffd3043f91b25996f8da34d7533394348d Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 12 Mar 2009 11:56:10 -0400 Subject: Update old Ur/Web library section, before adding new stuff --- doc/manual.tex | 34 +++++++++++++++++++++++----------- lib/ur/basis.urs | 4 ++-- 2 files changed, 25 insertions(+), 13 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 7a0a0002..fb056fd0 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1129,14 +1129,27 @@ The Ur compiler treates $\mt{folder}$ like a constructor class, using built-in r \section{The Ur/Web Standard Library} +\subsection{Monads} + +The Ur Basis defines the monad constructor class from Haskell. + +$$\begin{array}{l} + \mt{class} \; \mt{monad} :: \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{return} : \mt{m} ::: (\mt{Type} \to \mt{Type}) \to \mt{t} ::: \mt{Type} \\ + \hspace{.1in} \to \mt{monad} \; \mt{m} \\ + \hspace{.1in} \to \mt{t} \to \mt{m} \; \mt{t} \\ + \mt{val} \; \mt{bind} : \mt{m} ::: (\mt{Type} \to \mt{Type}) \to \mt{t1} ::: \mt{Type} \to \mt{t2} ::: \mt{Type} \\ + \hspace{.1in} \to \mt{monad} \; \mt{m} \\ + \hspace{.1in} \to \mt{m} \; \mt{t1} \to (\mt{t1} \to \mt{m} \; \mt{t2}) \\ + \hspace{.1in} \to \mt{m} \; \mt{t2} +\end{array}$$ + \subsection{Transactions} Ur is a pure language; we use Haskell's trick to support controlled side effects. The standard library defines a monad $\mt{transaction}$, meant to stand for actions that may be undone cleanly. By design, no other kinds of actions are supported. $$\begin{array}{l} \mt{con} \; \mt{transaction} :: \mt{Type} \to \mt{Type} \\ - \\ - \mt{val} \; \mt{return} : \mt{t} ::: \mt{Type} \to \mt{t} \to \mt{transaction} \; \mt{t} \\ - \mt{val} \; \mt{bind} : \mt{t_1} ::: \mt{Type} \to \mt{t_2} ::: \mt{Type} \to \mt{transaction} \; \mt{t_1} \to (\mt{t_1} \to \mt{transaction} \; \mt{t_2}) \to \mt{transaction} \; \mt{t_2} + \mt{val} \; \mt{transaction\_monad} : \mt{monad} \; \mt{transaction} \end{array}$$ \subsection{HTTP} @@ -1175,7 +1188,7 @@ $$\begin{array}{l} Queries are used by folding over their results inside transactions. $$\begin{array}{l} \mt{val} \; \mt{query} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \lambda [\mt{tables} \sim \mt{exps}] \Rightarrow \mt{state} ::: \mt{Type} \to \mt{sql\_query} \; \mt{tables} \; \mt{exps} \\ - \hspace{.1in} \to (\$(\mt{exps} \rc \mt{fold} \; (\lambda \mt{nm} \; (\mt{fields} :: \{\mt{Type}\}) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \$\mt{fields}] \rc \mt{acc}) \; [] \; \mt{tables}) \\ + \hspace{.1in} \to (\$(\mt{exps} \rc \mt{map} \; (\lambda \mt{fields} :: \{\mt{Type}\} \Rightarrow \$\mt{fields}) \; \mt{tables}) \\ \hspace{.2in} \to \mt{state} \to \mt{transaction} \; \mt{state}) \\ \hspace{.1in} \to \mt{state} \to \mt{transaction} \; \mt{state} \end{array}$$ @@ -1203,12 +1216,12 @@ $$\begin{array}{l} \hspace{.1in} \to \mt{grouped} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedFields} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedExps} ::: \{\mt{Type}\} \\ - \hspace{.1in} \to \{\mt{From} : \$(\mt{fold} \; (\lambda \mt{nm} \; (\mt{fields} :: \{\mt{Type}\}) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \mt{sql\_table} \; \mt{fields}] \rc \mt{acc}) \; [] \; \mt{tables}), \\ + \hspace{.1in} \to \{\mt{From} : \$(\mt{map} \; \mt{sql\_table} \; \mt{tables}), \\ \hspace{.2in} \mt{Where} : \mt{sql\_exp} \; \mt{tables} \; [] \; [] \; \mt{bool}, \\ \hspace{.2in} \mt{GroupBy} : \mt{sql\_subset} \; \mt{tables} \; \mt{grouped}, \\ \hspace{.2in} \mt{Having} : \mt{sql\_exp} \; \mt{grouped} \; \mt{tables} \; [] \; \mt{bool}, \\ \hspace{.2in} \mt{SelectFields} : \mt{sql\_subset} \; \mt{grouped} \; \mt{selectedFields}, \\ - \hspace{.2in} \mt {SelectExps} : \$(\mt{fold} \; (\lambda \mt{nm} \; (\mt{t} :: \mt{Type}) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \mt{sql\_exp} \; \mt{grouped} \; \mt{tables} \; [] \; \mt{t}] \rc \mt{acc}) \; [] \; \mt{selectedExps}) \} \\ + \hspace{.2in} \mt {SelectExps} : \$(\mt{map} \; (\mt{sql\_exp} \; \mt{grouped} \; \mt{tables} \; []) \; \mt{selectedExps}) \} \\ \hspace{.1in} \to \mt{sql\_query1} \; \mt{tables} \; \mt{selectedFields} \; \mt{selectedExps} \end{array}$$ @@ -1217,9 +1230,8 @@ $$\begin{array}{l} \mt{con} \; \mt{sql\_subset} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \mt{Type} \\ \mt{val} \; \mt{sql\_subset} : \mt{keep\_drop} :: \{(\{\mt{Type}\} \times \{\mt{Type}\})\} \\ \hspace{.1in} \to \mt{sql\_subset} \\ - \hspace{.2in} (\mt{fold} \; (\lambda \mt{nm} \; (\mt{fields} :: (\{\mt{Type}\} \times \{\mt{Type}\})) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \; [\mt{fields}.1 \sim \mt{fields}.2] \Rightarrow \\ - \hspace{.3in} [\mt{nm} = \mt{fields}.1 \rc \mt{fields}.2] \rc \mt{acc}) \; [] \; \mt{keep\_drop}) \\ - \hspace{.2in} (\mt{fold} \; (\lambda \mt{nm} \; (\mt{fields} :: (\{\mt{Type}\} \times \{\mt{Type}\})) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \mt{fields}.1] \rc \mt{acc}) \; [] \; \mt{keep\_drop}) \\ + \hspace{.2in} (\mt{map} \; (\lambda \mt{fields} :: (\{\mt{Type}\} \times \{\mt{Type}\}) \Rightarrow \mt{fields}.1 \rc \mt{fields}.2)\; \mt{keep\_drop}) \\ + \hspace{.2in} (\mt{map} \; (\lambda \mt{fields} :: (\{\mt{Type}\} \times \{\mt{Type}\}) \Rightarrow \mt{fields}.1) \; \mt{keep\_drop}) \\ \mt{val} \; \mt{sql\_subset\_all} : \mt{tables} :: \{\{\mt{Type}\}\} \to \mt{sql\_subset} \; \mt{tables} \; \mt{tables} \end{array}$$ @@ -1363,13 +1375,13 @@ $$\begin{array}{l} Properly-typed records may be used to form $\mt{INSERT}$ commands. $$\begin{array}{l} \mt{val} \; \mt{insert} : \mt{fields} ::: \{\mt{Type}\} \to \mt{sql\_table} \; \mt{fields} \\ - \hspace{.1in} \to \$(\mt{fold} \; (\lambda \mt{nm} \; (\mt{t} :: \mt{Type}) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \mt{sql\_exp} \; [] \; [] \; [] \; \mt{t}] \rc \mt{acc}) \; [] \; \mt{fields}) \to \mt{dml} + \hspace{.1in} \to \$(\mt{map} \; (\mt{sql\_exp} \; [] \; [] \; []) \; \mt{fields}) \to \mt{dml} \end{array}$$ An $\mt{UPDATE}$ command is formed from a choice of which table fields to leave alone and which to change, along with an expression to use to compute the new value of each changed field and a $\mt{WHERE}$ clause. $$\begin{array}{l} \mt{val} \; \mt{update} : \mt{unchanged} ::: \{\mt{Type}\} \to \mt{changed} :: \{\mt{Type}\} \to \lambda [\mt{changed} \sim \mt{unchanged}] \\ - \hspace{.1in} \Rightarrow \$(\mt{fold} \; (\lambda \mt{nm} \; (\mt{t} :: \mt{Type}) \; \mt{acc} \; [[\mt{nm}] \sim \mt{acc}] \Rightarrow [\mt{nm} = \mt{sql\_exp} \; [\mt{T} = \mt{changed} \rc \mt{unchanged}] \; [] \; [] \; \mt{t}] \rc \mt{acc}) \; [] \; \mt{changed}) \\ + \hspace{.1in} \Rightarrow \$(\mt{map} \; (\mt{sql\_exp} \; [\mt{T} = \mt{changed} \rc \mt{unchanged}] \; [] \; []) \; \mt{changed}) \\ \hspace{.1in} \to \mt{sql\_table} \; (\mt{changed} \rc \mt{unchanged}) \to \mt{sql\_exp} \; [\mt{T} = \mt{changed} \rc \mt{unchanged}] \; [] \; [] \; \mt{bool} \to \mt{dml} \end{array}$$ diff --git a/lib/ur/basis.urs b/lib/ur/basis.urs index c2a55168..e4bff8a9 100644 --- a/lib/ur/basis.urs +++ b/lib/ur/basis.urs @@ -128,12 +128,12 @@ val sql_query1 : tables ::: {{Type}} -> grouped ::: {{Type}} -> selectedFields ::: {{Type}} -> selectedExps ::: {Type} - -> {From : $(map (fn fields :: {Type} => sql_table fields) tables), + -> {From : $(map sql_table tables), Where : sql_exp tables [] [] bool, GroupBy : sql_subset tables grouped, Having : sql_exp grouped tables [] bool, SelectFields : sql_subset grouped selectedFields, - SelectExps : $(map (fn (t :: Type) => sql_exp grouped tables [] t) selectedExps) } + SelectExps : $(map (sql_exp grouped tables []) selectedExps) } -> sql_query1 tables selectedFields selectedExps type sql_relop -- cgit v1.2.3 From 4bdb9cdaf35e83082260e91556c35590028282d9 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 12 Mar 2009 12:10:02 -0400 Subject: Functional-reactive subsection --- doc/manual.tex | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index fb056fd0..b57953ea 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1449,6 +1449,34 @@ $$\begin{array}{l} \end{array}$$ +\subsection{Functional-Reactive Client-Side Programming} + +Ur/Web supports running code on web browsers, via automatic compilation to JavaScript. Most approaches to this kind of coding involve imperative manipulation of the DOM tree representing an HTML document's structure. Ur/Web follows the \emph{functional-reactive} approach instead. Programs may allocate mutable \emph{sources} of arbitrary types, and an HTML page is effectively a pure function over the latest values of the sources. The page is not mutated directly, but rather it changes automatically as the sources are mutated. + +$$\begin{array}{l} + \mt{con} \; \mt{source} :: \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{source} : \mt{t} ::: \mt{Type} \to \mt{t} \to \mt{transaction} \; (\mt{source} \; \mt{t}) \\ + \mt{val} \; \mt{set} : \mt{t} ::: \mt{Type} \to \mt{source} \; \mt{t} \to \mt{t} \to \mt{transaction} \; \mt{unit} \\ + \mt{val} \; \mt{get} : \mt{t} ::: \mt{Type} \to \mt{source} \; \mt{t} \to \mt{transaction} \; \mt{t} +\end{array}$$ + +Pure functions over sources are represented in a monad of \emph{signals}. + +$$\begin{array}{l} + \mt{con} \; \mt{signal} :: \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{signal\_monad} : \mt{monad} \; \mt{signal} \\ + \mt{val} \; \mt{signal} : \mt{t} ::: \mt{Type} \to \mt{source} \; \mt{t} \to \mt{signal} \; \mt{t} +\end{array}$$ + +A reactive portion of an HTML page is injected with a $\mt{dyn}$ tag, which has a signal-valued attribute $\mt{Signal}$. + +$$\begin{array}{l} + \mt{val} \; \mt{dyn} : \mt{ctx} ::: \{\mt{Unit}\} \to \mt{use} ::: \{\mt{Type}\} \to \mt{bind} ::: \{\mt{Type}\} \to \mt{unit} \\ + \hspace{.1in} \to \mt{tag} \; [\mt{Signal} = \mt{signal} \; (\mt{xml} \; \mt{ctx} \; \mt{use} \; \mt{bind})] \; \mt{ctx} \; [] \; \mt{use} \; \mt{bind} +\end{array}$$ + +Transactions can be run on the client by including them in attributes like the $\mt{OnClick}$ attribute of $\mt{button}$, and GUI widgets like $\mt{ctextbox}$ have $\mt{Source}$ attributes that can be used to connect them to sources, so that their values can be read by code running because of, e.g., an $\mt{OnClick}$ event. + \section{Ur/Web Syntax Extensions} Ur/Web features some syntactic shorthands for building values using the functions from the last section. This section sketches the grammar of those extensions. We write spans of syntax inside brackets to indicate that they are optional. -- cgit v1.2.3 From 8947397e8e7133f0f284211f0e12f662728c1a35 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 12 Mar 2009 12:18:11 -0400 Subject: Describe AJAX RPC structure --- doc/manual.tex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index b57953ea..72360bb8 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1573,6 +1573,8 @@ HTML forms are handled in a similar way. The $\mt{action}$ attribute of a $\mt{ For both links and actions, direct arguments and local variables mentioned implicitly via closures are automatically included in serialized form in URLs, in the order in which they appear in the source code. +Ur/Web programs generally mix server- and client-side code in a fairly transparent way. The one important restriction is that mixed client-server code must encapsulate all server-side pieces within named functions. This is because execution of such pieces will be implemented by explicit calls to the remote web server, and it is useful to get the programmer's help in designing the interface to be used. For example, this makes it easier to allow a client running an old version of an application to continue interacting with a server that has been upgraded to a new version, if the programmer took care to keep the interfaces of all of the old remote calls the same. The functions implementing these services are assigned names in the same way as normal web entry points, by using module structure. + \section{Compiler Phases} -- cgit v1.2.3 From 64c858689d78c4ed5a83363207faca5ecb2cbb91 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 12 Mar 2009 12:23:23 -0400 Subject: Update compiler phases --- doc/manual.tex | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 72360bb8..0038d3b1 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1610,6 +1610,14 @@ Remove unnecessary mutual recursion, splitting recursive groups into strongly-co Remove all definitions not needed to run the page handlers that are visible in the signature of the last module listed in the \texttt{.urp} file. +\subsection{Rpcify} + +Pieces of code are determined to be client-side, server-side, neither, or both, by figuring out which standard library functions might be needed to execute them. Calls to server-side functions (e.g., $\mt{query}$) within mixed client-server code are identified and replaced with explicit remote calls. Some mixed functions may be converted to continuation-passing style to facilitate this transformation. + +\subsection{Untangle, Shake} + +Repeat these simplifications. + \subsection{\label{tag}Tag} Assign a URL name to each link and form action. It is important that these links and actions are written as applications of named functions, because such names are used to generate URL patterns. A URL pattern has a name built from the full module path of the named function, followed by the function name, with all pieces separated by slashes. The path of a functor application is based on the name given to the result, rather than the path of the functor itself. -- cgit v1.2.3 From c826168d7e72d3b5454ef581b61faaf11ff3be68 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 12 Mar 2009 12:25:05 -0400 Subject: Add guard elim rule --- doc/manual.tex | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 0038d3b1..abce0c44 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -665,6 +665,10 @@ $$\infer{\Gamma \vdash \lambda [c_1 \sim c_2] \Rightarrow e : \lambda [c_1 \sim \Gamma \vdash c_1 :: \{\kappa\} & \Gamma \vdash c_2 :: \{\kappa'\} & \Gamma, c_1 \sim c_2 \vdash e : \tau +} +\quad \infer{\Gamma \vdash e \; ! : \tau}{ + \Gamma \vdash e : [c_1 \sim c_2] \Rightarrow \tau + & \Gamma \vdash c_1 \sim c_2 }$$ \subsection{Pattern Typing} -- cgit v1.2.3 From e3fe4307964fd93ddac266cf125ff2264fde2656 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 12 Mar 2009 12:34:39 -0400 Subject: Spell-check manual --- doc/manual.tex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index abce0c44..d52927ea 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1128,7 +1128,7 @@ $$\begin{array}{l} For a type-level record $\mt{r}$, a $\mt{folder} \; \mt{r}$ encodes a permutation of $\mt{r}$'s elements. The $\mt{fold}$ function can be called on a $\mt{folder}$ to iterate over the elements of $\mt{r}$ in that order. $\mt{fold}$ is parameterized on a type-level function to be used to calculate the type of each intermediate result of folding. After processing a subset $\mt{r'}$ of $\mt{r}$'s entries, the type of the accumulator should be $\mt{tf} \; \mt{r'}$. The next two expression arguments to $\mt{fold}$ are the usual step function and initial accumulator, familiar from fold functions over lists. The final two arguments are the record to fold over and a $\mt{folder}$ for it. -The Ur compiler treates $\mt{folder}$ like a constructor class, using built-in rules to infer $\mt{folder}$s for records with known structure. The order in which field names are mentioned in source code is used as a hint about the permutation that the programmer would like. +The Ur compiler treats $\mt{folder}$ like a constructor class, using built-in rules to infer $\mt{folder}$s for records with known structure. The order in which field names are mentioned in source code is used as a hint about the permutation that the programmer would like. \section{The Ur/Web Standard Library} @@ -1647,6 +1647,7 @@ Here the compiler repeats the earlier Shake phase. Programs are translated to a new intermediate language without polymorphism or non-$\mt{Type}$ constructors. Error messages may pop up here if earlier phases failed to remove such features. This is the stage at which concrete names are generated for cookies, tables, and sequences. They are named following the same convention as for links and actions, based on module path information saved from earlier stages. Table and sequence names separate path elements with underscores instead of slashes, and they are prefixed by \texttt{uw\_}. + \subsection{MonoOpt} Simple algebraic laws are applied to simplify the program, focusing especially on efficient imperative generation of HTML pages. -- cgit v1.2.3 From 50aed147862bf5fb1b94528d254013f259f6eba8 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 5 Apr 2009 12:37:38 -0400 Subject: Update the manual --- doc/manual.tex | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++----- lib/ur/basis.urs | 1 - 2 files changed, 52 insertions(+), 6 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index d52927ea..b5eb191d 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -129,6 +129,9 @@ For each entry \texttt{M} in the module list, the file \texttt{M.urs} is include A few other named directives are supported. \texttt{prefix PREFIX} sets the prefix included before every URI within the generated application; the default is \texttt{/}. \texttt{exe FILENAME} sets the filename to which to write the output executable; the default for file \texttt{P.urp} is \texttt{P.exe}. \texttt{debug} saves some intermediate C files, which is mostly useful to help in debugging the compiler itself. \texttt{profile} generates an executable that may be used with gprof. +\texttt{timeout N} sets to \texttt{N} seconds the amount of time that the generated server will wait after the last contact from a client before determining that that client has exited the application. Clients that remain active will take the timeout setting into account in determining how often to ping the server, so it only makes sense to set a high timeout to cope with browser and network delays and failures. Higher timeouts can lead to more unnecessary client information taking up memory on the server. The timeout goes unused by any page that doesn't involve the \texttt{recv} function, since the server only needs to store per-client information for clients that receive asynchronous messages. + + \subsection{Building an Application} To compile project \texttt{P.urp}, simply run @@ -1453,9 +1456,30 @@ $$\begin{array}{l} \end{array}$$ -\subsection{Functional-Reactive Client-Side Programming} +\subsection{Client-Side Programming} + +Ur/Web supports running code on web browsers, via automatic compilation to JavaScript. + +\subsubsection{The Basics} + +Clients can open alert dialog boxes, in the usual annoying JavaScript way. +$$\begin{array}{l} + \mt{val} \; \mt{alert} : \mt{string} \to \mt{transaction} \; \mt{unit} +\end{array}$$ + +Any transaction may be run in a new thread with the $\mt{spawn}$ function. +$$\begin{array}{l} + \mt{val} \; \mt{spawn} : \mt{transaction} \; \mt{unit} \to \mt{transaction} \; \mt{unit} +\end{array}$$ + +The current thread can be paused for at least a specified number of milliseconds. +$$\begin{array}{l} + \mt{val} \; \mt{sleep} : \mt{int} \to \mt{transaction} \; \mt{unit} +\end{array}$$ + +\subsubsection{Functional-Reactive Page Generation} -Ur/Web supports running code on web browsers, via automatic compilation to JavaScript. Most approaches to this kind of coding involve imperative manipulation of the DOM tree representing an HTML document's structure. Ur/Web follows the \emph{functional-reactive} approach instead. Programs may allocate mutable \emph{sources} of arbitrary types, and an HTML page is effectively a pure function over the latest values of the sources. The page is not mutated directly, but rather it changes automatically as the sources are mutated. +Most approaches to ``AJAX''-style coding involve imperative manipulation of the DOM tree representing an HTML document's structure. Ur/Web follows the \emph{functional-reactive} approach instead. Programs may allocate mutable \emph{sources} of arbitrary types, and an HTML page is effectively a pure function over the latest values of the sources. The page is not mutated directly, but rather it changes automatically as the sources are mutated. $$\begin{array}{l} \mt{con} \; \mt{source} :: \mt{Type} \to \mt{Type} \\ @@ -1475,11 +1499,34 @@ $$\begin{array}{l} A reactive portion of an HTML page is injected with a $\mt{dyn}$ tag, which has a signal-valued attribute $\mt{Signal}$. $$\begin{array}{l} - \mt{val} \; \mt{dyn} : \mt{ctx} ::: \{\mt{Unit}\} \to \mt{use} ::: \{\mt{Type}\} \to \mt{bind} ::: \{\mt{Type}\} \to \mt{unit} \\ - \hspace{.1in} \to \mt{tag} \; [\mt{Signal} = \mt{signal} \; (\mt{xml} \; \mt{ctx} \; \mt{use} \; \mt{bind})] \; \mt{ctx} \; [] \; \mt{use} \; \mt{bind} + \mt{val} \; \mt{dyn} : \mt{use} ::: \{\mt{Type}\} \to \mt{bind} ::: \{\mt{Type}\} \to \mt{unit} \\ + \hspace{.1in} \to \mt{tag} \; [\mt{Signal} = \mt{signal} \; (\mt{xml} \; \mt{body} \; \mt{use} \; \mt{bind})] \; \mt{body} \; [] \; \mt{use} \; \mt{bind} +\end{array}$$ + +Transactions can be run on the client by including them in attributes like the $\mt{Onclick}$ attribute of $\mt{button}$, and GUI widgets like $\mt{ctextbox}$ have $\mt{Source}$ attributes that can be used to connect them to sources, so that their values can be read by code running because of, e.g., an $\mt{Onclick}$ event. + +\subsubsection{Asynchronous Message-Passing} + +To support asynchronous, ``server push'' delivery of messages to clients, any client that might need to receive an asynchronous message is assigned a unique ID. These IDs may be retrieved both on the client and on the server, during execution of code related to a client. + +$$\begin{array}{l} + \mt{type} \; \mt{client} \\ + \mt{val} \; \mt{self} : \mt{transaction} \; \mt{client} \end{array}$$ -Transactions can be run on the client by including them in attributes like the $\mt{OnClick}$ attribute of $\mt{button}$, and GUI widgets like $\mt{ctextbox}$ have $\mt{Source}$ attributes that can be used to connect them to sources, so that their values can be read by code running because of, e.g., an $\mt{OnClick}$ event. +\emph{Channels} are the means of message-passing. Each channel is created in the context of a client and belongs to that client; no other client may receive the channel's messages. Each channel type includes the type of values that may be sent over the channel. Sending and receiving are asynchronous, in the sense that a client need not be ready to receive a message right away. Rather, sent messages may queue up, waiting to be processed. + +$$\begin{array}{l} + \mt{con} \; \mt{channel} :: \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{channel} : \mt{t} ::: \mt{Type} \to \mt{transaction} \; (\mt{channel} \; \mt{t}) \\ + \mt{val} \; \mt{send} : \mt{t} ::: \mt{Type} \to \mt{channel} \; \mt{t} \to \mt{t} \to \mt{transaction} \; \mt{unit} \\ + \mt{val} \; \mt{recv} : \mt{t} ::: \mt{Type} \to \mt{channel} \; \mt{t} \to \mt{transaction} \; \mt{t} +\end{array}$$ + +The $\mt{channel}$ and $\mt{send}$ operations may only be executed on the server, and $\mt{recv}$ may only be executed on a client. Neither clients nor channels may be passed as arguments from clients to server-side functions, so persistent channels can only be maintained by storing them in the database and looking them up using the current client ID or some application-specific value as a key. + +Clients and channels live only as long as the web browser page views that they are associated with. When a user surfs away, his client and its channels will be garbage-collected, after that user is not heard from for the timeout period. Garbage collection deletes any database row that contains a client or channel directly. Any reference to one of these types inside an $\mt{option}$ is set to $\mt{None}$ instead. Both kinds of handling have the flavor of weak pointers, and that is a useful way to think about clients and channels in the database. + \section{Ur/Web Syntax Extensions} diff --git a/lib/ur/basis.urs b/lib/ur/basis.urs index 0d5d3d71..1cbca61d 100644 --- a/lib/ur/basis.urs +++ b/lib/ur/basis.urs @@ -113,7 +113,6 @@ val sleep : int -> transaction unit con channel :: Type -> Type val channel : t ::: Type -> transaction (channel t) -val subscribe : t ::: Type -> channel t -> transaction unit val send : t ::: Type -> channel t -> t -> transaction unit val recv : t ::: Type -> channel t -> transaction t -- cgit v1.2.3 From b250dac52673020574c2814081699a9a2ee9608b Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 5 May 2009 11:45:03 -0400 Subject: Revising manual through end of Section 3 --- doc/manual.tex | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index b5eb191d..3f549c5b 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -56,10 +56,10 @@ make sudo make install \end{verbatim} -Some other packages must be installed for the above to work. At a minimum, you need a standard UNIX shell, with standard UNIX tools like sed and GCC in your execution path; and MLton, the whole-program optimizing compiler for Standard ML. To build programs that access SQL databases, you also need libpq, the PostgreSQL client library. As of this writing, in the ``testing'' version of Debian Linux, this command will install the more uncommon of these dependencies: +Some other packages must be installed for the above to work. At a minimum, you need a standard UNIX shell, with standard UNIX tools like sed and GCC in your execution path; MLton, the whole-program optimizing compiler for Standard ML; and the mhash C library. To build programs that access SQL databases, you also need libpq, the PostgreSQL client library. As of this writing, in the ``testing'' version of Debian Linux, this command will install the more uncommon of these dependencies: \begin{verbatim} -apt-get install mlton libpq-dev +apt-get install mlton libmhash-dev libpq-dev \end{verbatim} It is also possible to access the modules of the Ur/Web compiler interactively, within Standard ML of New Jersey. To install the prerequisites in Debian testing: @@ -127,9 +127,27 @@ A blank line always separates the named directives from a list of modules to inc For each entry \texttt{M} in the module list, the file \texttt{M.urs} is included in the project if it exists, and the file \texttt{M.ur} must exist and is always included. -A few other named directives are supported. \texttt{prefix PREFIX} sets the prefix included before every URI within the generated application; the default is \texttt{/}. \texttt{exe FILENAME} sets the filename to which to write the output executable; the default for file \texttt{P.urp} is \texttt{P.exe}. \texttt{debug} saves some intermediate C files, which is mostly useful to help in debugging the compiler itself. \texttt{profile} generates an executable that may be used with gprof. - -\texttt{timeout N} sets to \texttt{N} seconds the amount of time that the generated server will wait after the last contact from a client before determining that that client has exited the application. Clients that remain active will take the timeout setting into account in determining how often to ping the server, so it only makes sense to set a high timeout to cope with browser and network delays and failures. Higher timeouts can lead to more unnecessary client information taking up memory on the server. The timeout goes unused by any page that doesn't involve the \texttt{recv} function, since the server only needs to store per-client information for clients that receive asynchronous messages. +Here is the complete list of directive forms. ``FFI'' stands for ``foreign function interface,'' Ur's facility for interaction between Ur programs and C and JavaScript libraries. +\begin{itemize} +\item \texttt{[allow|deny] [url|mime] PATTERN} registers a rule governing which URLs or MIME types are allowed in this application. The first such rule to match a URL or MIME type determines the verdict. If \texttt{PATTERN} ends in \texttt{*}, it is interpreted as a prefix rule. Otherwise, a string must match it exactly. +\item \texttt{clientOnly Module.ident} registers an FFI function or transaction that may only be run in client browsers. +\item \texttt{clientToServer Module.ident} adds FFI type \texttt{Module.ident} to the list of types that are OK to marshal from clients to servers. Values like XML trees and SQL queries are hard to marshal without introducing expensive validity checks, so it's easier to ensure that the server never trusts clients to send such values. The file \texttt{include/urweb.h} shows examples of the C support functions that are required of any type that may be marshalled. These include \texttt{attrify}, \texttt{urlify}, and \texttt{unurlify} functions. +\item \texttt{database DBSTRING} sets the string to pass to libpq to open a database connection. +\item \texttt{debug} saves some intermediate C files, which is mostly useful to help in debugging the compiler itself. +\item \texttt{effectful Module.ident} registers an FFI function or transaction as having side effects. The optimizer avoids removing, moving, or duplicating calls to such functions. Every effectful FFI function must be registered, or the optimizer may make invalid transformations. +\item \texttt{exe FILENAME} sets the filename to which to write the output executable. The default for file \texttt{P.urp} is \texttt{P.exe}. +\item \texttt{ffi FILENAME} reads the file \texttt{FILENAME.urs} to determine the interface to a new FFI module. The name of the module is calculated from \texttt{FILENAME} in the same way as for normal source files. See the files \texttt{include/urweb.h} and \texttt{src/c/urweb.c} for examples of C headers and implementations for FFI modules. In general, every type or value \texttt{Module.ident} becomes \texttt{uw\_Module\_ident} in C. +\item \texttt{jsFunc Module.ident=name} gives the JavaScript name of an FFI value. +\item \texttt{library FILENAME} parses \texttt{FILENAME.urp} and merges its contents with the rest of the current file's contents. +\item \texttt{link FILENAME} adds \texttt{FILENAME} to the list of files to be passed to the GCC linker at the end of compilation. This is most useful for importing extra libraries needed by new FFI modules. +\item \texttt{prefix PREFIX} sets the prefix included before every URI within the generated application. The default is \texttt{/}. +\item \texttt{profile} generates an executable that may be used with gprof. +\item \texttt{rewrite KIND FROM TO} gives a rule for rewriting canonical module paths. For instance, the canonical path of a page may be \texttt{Mod1.Mod2.mypage}, while you would rather the page were accessed via a URL containing only \texttt{page}. The directive \texttt{rewrite url Mod1/Mod2/mypage page} would accomplish that. The possible values of \texttt{KIND} determine which kinds of objects are affected. The kind \texttt{all} matches any object, and \texttt{url} matches page URLs. The kinds \texttt{table}, \texttt{sequence}, and \texttt{view} match those sorts of SQL entities, and \texttt{relation} matches any of those three. \texttt{cookie} matches HTTP cookies, and \texttt{style} matches CSS class names. If \texttt{FROM} ends in \texttt{/*}, it is interpreted as a prefix matching rule, and rewriting occurs by replacing only the appropriate prefix of a path with \texttt{TO}. While the actual external names of relations and styles have parts separated by underscores instead of slashes, all rewrite rules must be written in terms of slashes. +\item \texttt{script URL} adds \texttt{URL} to the list of extra JavaScript files to be included at the beginning of any page that uses JavaScript. This is most useful for importing JavaScript versions of functions found in new FFI modules. +\item \texttt{serverOnly Module.ident} registers an FFI function or transaction that may only be run on the server. +\item \texttt{sql FILENAME} sets where to write an SQL file with the commands to create the expected database schema. The default is not to create such a file. +\item \texttt{timeout N} sets to \texttt{N} seconds the amount of time that the generated server will wait after the last contact from a client before determining that that client has exited the application. Clients that remain active will take the timeout setting into account in determining how often to ping the server, so it only makes sense to set a high timeout to cope with browser and network delays and failures. Higher timeouts can lead to more unnecessary client information taking up memory on the server. The timeout goes unused by any page that doesn't involve the \texttt{recv} function, since the server only needs to store per-client information for clients that receive asynchronous messages. +\end{itemize} \subsection{Building an Application} -- cgit v1.2.3 From 3840a47b19607d0cf8136bbdaa9b688a63328819 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 5 May 2009 11:59:50 -0400 Subject: Revising manual through end of Section 6 --- doc/manual.tex | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 3f549c5b..368e3024 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -166,7 +166,7 @@ urweb -timing P \section{Ur Syntax} -In this section, we describe the syntax of Ur, deferring to a later section discussion of most of the syntax specific to SQL and XML. The sole exceptions are the declaration forms for tables, sequences, and cookies. +In this section, we describe the syntax of Ur, deferring to a later section discussion of most of the syntax specific to SQL and XML. The sole exceptions are the declaration forms for relations, cookies, and styles. \subsection{Lexical Conventions} @@ -343,8 +343,10 @@ $$\begin{array}{rrcll} &&& \mt{constraint} \; c \sim c & \textrm{record disjointness constraint} \\ &&& \mt{open} \; \mt{constraints} \; M & \textrm{inclusion of just the constraints from a module} \\ &&& \mt{table} \; x : c & \textrm{SQL table} \\ + &&& \mt{view} \; x : c & \textrm{SQL view} \\ &&& \mt{sequence} \; x & \textrm{SQL sequence} \\ &&& \mt{cookie} \; x : \tau & \textrm{HTTP cookie} \\ + &&& \mt{style} \; x : \tau & \textrm{CSS class} \\ &&& \mt{class} \; x :: \kappa = c & \textrm{concrete constructor class} \\ \\ \textrm{Modules} & M &::=& \mt{struct} \; d^* \; \mt{end} & \textrm{constant} \\ @@ -356,6 +358,8 @@ $$\begin{array}{rrcll} There are two kinds of Ur files. A file named $M\texttt{.ur}$ is an \emph{implementation file}, and it should contain a sequence of declarations $d^*$. A file named $M\texttt{.urs}$ is an \emph{interface file}; it must always have a matching $M\texttt{.ur}$ and should contain a sequence of signature items $s^*$. When both files are present, the overall effect is the same as a monolithic declaration $\mt{structure} \; M : \mt{sig} \; s^* \; \mt{end} = \mt{struct} \; d^* \; \mt{end}$. When no interface file is included, the overall effect is similar, with a signature for module $M$ being inferred rather than just checked against an interface. +We omit some extra possibilities in $\mt{table}$ syntax, deferring them to Section \ref{tables}. + \subsection{Shorthands} There are a variety of derived syntactic forms that elaborate into the core syntax from the last subsection. We will present the additional forms roughly following the order in which we presented the constructs that they elaborate into. @@ -392,7 +396,7 @@ The syntax $\mt{if} \; e \; \mt{then} \; e_1 \; \mt{else} \; e_2$ expands to $\m There are infix operator syntaxes for a number of functions defined in the $\mt{Basis}$ module. There is $=$ for $\mt{eq}$, $\neq$ for $\mt{neq}$, $-$ for $\mt{neg}$ (as a prefix operator) and $\mt{minus}$, $+$ for $\mt{plus}$, $\times$ for $\mt{times}$, $/$ for $\mt{div}$, $\%$ for $\mt{mod}$, $<$ for $\mt{lt}$, $\leq$ for $\mt{le}$, $>$ for $\mt{gt}$, and $\geq$ for $\mt{ge}$. -A signature item $\mt{table} \; x : c$ is shorthand for $\mt{val} \; x : \mt{Basis}.\mt{sql\_table} \; c$. $\mt{sequence} \; x$ is short for $\mt{val} \; x : \mt{Basis}.\mt{sql\_sequence}$, and $\mt{cookie} \; x : \tau$ is shorthand for $\mt{val} \; x : \mt{Basis}.\mt{http\_cookie} \; \tau$. +A signature item $\mt{table} \; x : c$ is shorthand for $\mt{val} \; x : \mt{Basis}.\mt{sql\_table} \; c \; []$. $\mt{view} \; x : c$ is shorthand for $\mt{val} \; x : \mt{Basis}.\mt{sql\_view} \; c$, $\mt{sequence} \; x$ is short for $\mt{val} \; x : \mt{Basis}.\mt{sql\_sequence}$. $\mt{cookie} \; x : \tau$ is shorthand for $\mt{val} \; x : \mt{Basis}.\mt{http\_cookie} \; \tau$, and $\mt{style} \; x$ is shorthand for $\mt{val} \; x : \mt{Basis}.\mt{css\_class}$. \section{Static Semantics} @@ -790,17 +794,22 @@ $$\infer{\Gamma \vdash \mt{constraint} \; c_1 \sim c_2 \leadsto \Gamma}{ \Gamma \vdash M : \mt{sig} \; \overline{s} \; \mt{end} }$$ -$$\infer{\Gamma \vdash \mt{table} \; x : c \leadsto \Gamma, x : \mt{Basis}.\mt{sql\_table} \; c}{ +$$\infer{\Gamma \vdash \mt{table} \; x : c \leadsto \Gamma, x : \mt{Basis}.\mt{sql\_table} \; c \; []}{ \Gamma \vdash c :: \{\mt{Type}\} } -\quad \infer{\Gamma \vdash \mt{sequence} \; x \leadsto \Gamma, x : \mt{Basis}.\mt{sql\_sequence}}{}$$ +\quad \infer{\Gamma \vdash \mt{view} \; x : c \leadsto \Gamma, x : \mt{Basis}.\mt{sql\_view} \; c}{ + \Gamma \vdash c :: \{\mt{Type}\} +}$$ + +$$\infer{\Gamma \vdash \mt{sequence} \; x \leadsto \Gamma, x : \mt{Basis}.\mt{sql\_sequence}}{}$$ $$\infer{\Gamma \vdash \mt{cookie} \; x : \tau \leadsto \Gamma, x : \mt{Basis}.\mt{http\_cookie} \; \tau}{ \Gamma \vdash \tau :: \mt{Type} -}$$ +} +\quad \infer{\Gamma \vdash \mt{style} \; x \leadsto \Gamma, x : \mt{Basis}.\mt{css\_class}}{}$$ -$$\infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leadsto \Gamma, x :: \kappa \to \mt{Type} = c}{ - \Gamma \vdash c :: \kappa \to \mt{Type} +$$\infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leadsto \Gamma, x :: \kappa = c}{ + \Gamma \vdash c :: \kappa }$$ $$\infer{\overline{y}; x; \Gamma \vdash \cdot \leadsto \Gamma}{} @@ -856,10 +865,10 @@ $$\infer{\Gamma \vdash \mt{constraint} \; c_1 \sim c_2 \leadsto \Gamma, c_1 \sim & \Gamma \vdash c_2 :: \{\kappa\} }$$ -$$\infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leadsto \Gamma, x :: \kappa \to \mt{Type} = c}{ - \Gamma \vdash c :: \kappa \to \mt{Type} +$$\infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leadsto \Gamma, x :: \kappa = c}{ + \Gamma \vdash c :: \kappa } -\quad \infer{\Gamma \vdash \mt{class} \; x :: \kappa \leadsto \Gamma, x :: \kappa \to \mt{Type}}{}$$ +\quad \infer{\Gamma \vdash \mt{class} \; x :: \kappa \leadsto \Gamma, x :: \kappa}{}$$ \subsection{Signature Compatibility} @@ -919,13 +928,13 @@ $$\infer{\Gamma \vdash \mt{datatype} \; x = \mt{datatype} \; M.z \leq \mt{con} \ & \mt{proj}(M, \overline{s}, \mt{datatype} \; z) = (\overline{y}, \overline{dc}) }$$ -$$\infer{\Gamma \vdash \mt{class} \; x :: \kappa \leq \mt{con} \; x :: \kappa \to \mt{Type}}{} -\quad \infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leq \mt{con} \; x :: \kappa \to \mt{Type}}{}$$ +$$\infer{\Gamma \vdash \mt{class} \; x :: \kappa \leq \mt{con} \; x :: \kappa}{} +\quad \infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leq \mt{con} \; x :: \kappa}{}$$ $$\infer{\Gamma \vdash \mt{con} \; x :: \kappa = c_1 \leq \mt{con} \; x :: \mt{\kappa} = c_2}{ \Gamma \vdash c_1 \equiv c_2 } -\quad \infer{\Gamma \vdash \mt{class} \; x :: \kappa = c_1 \leq \mt{con} \; x :: \kappa \to \mt{Type} = c_2}{ +\quad \infer{\Gamma \vdash \mt{class} \; x :: \kappa = c_1 \leq \mt{con} \; x :: \kappa = c_2}{ \Gamma \vdash c_1 \equiv c_2 }$$ @@ -1019,8 +1028,10 @@ $$\infer{\Gamma \vdash M_1(M_2) : [X \mapsto M_2]S_2}{ \mt{sigOf}(\mt{constraint} \; c_1 \sim c_2) &=& \mt{constraint} \; c_1 \sim c_2 \\ \mt{sigOf}(\mt{open} \; \mt{constraints} \; M) &=& \cdot \\ \mt{sigOf}(\mt{table} \; x : c) &=& \mt{table} \; x : c \\ + \mt{sigOf}(\mt{view} \; x : c) &=& \mt{view} \; x : c \\ \mt{sigOf}(\mt{sequence} \; x) &=& \mt{sequence} \; x \\ \mt{sigOf}(\mt{cookie} \; x : \tau) &=& \mt{cookie} \; x : \tau \\ + \mt{sigOf}(\mt{style} \; x) &=& \mt{style} \; x \\ \mt{sigOf}(\mt{class} \; x :: \kappa = c) &=& \mt{class} \; x :: \kappa = c \\ \end{eqnarray*} \begin{eqnarray*} @@ -1098,9 +1109,9 @@ The type inference engine tries to take advantage of the algebraic rules governi \subsection{\label{typeclasses}Constructor Classes} -Ur includes a constructor class facility inspired by Haskell's. The current version is very rudimentary, only supporting instances for particular constructors built up from abstract constructors and datatypes and type-level application. +Ur includes a constructor class facility inspired by Haskell's. The current version is experimental, with very general Prolog-like facilities that can lead to compile-time non-termination. -Constructor classes are integrated with the module system. A constructor class of kind $\kappa$ is just a constructor of kind $\kappa \to \mt{Type}$. By marking such a constructor $c$ as a constructor class, the programmer instructs the type inference engine to, in each scope, record all values of types $c \; c'$ as \emph{instances}. Any function argument whose type is of such a form is treated as implicit, to be determined by examining the current instance database. +Constructor classes are integrated with the module system. A constructor class of kind $\kappa$ is just a constructor of kind $\kappa$. By marking such a constructor $c$ as a constructor class, the programmer instructs the type inference engine to, in each scope, record all values of types $c \; c_1 \; \ldots \; c_n$ as \emph{instances}. Any function argument whose type is of such a form is treated as implicit, to be determined by examining the current instance database. The ``dictionary encoding'' often used in Haskell implementations is made explicit in Ur. Constructor class instances are just properly-typed values, and they can also be considered as ``proofs'' of membership in the class. In some cases, it is useful to pass these proofs around explicitly. An underscore written where a proof is expected will also be inferred, if possible, from the current instance database. @@ -1195,6 +1206,8 @@ $$\begin{array}{l} \mt{con} \; \mt{sql\_table} :: \{\mt{Type}\} \to \mt{Type} \end{array}$$ +\subsubsection{\label{tables}Tables} + \subsubsection{Queries} A final query is constructed via the $\mt{sql\_query}$ function. Constructor arguments respectively specify the table fields we select (as records mapping tables to the subsets of their fields that we choose) and the (always named) extra expressions that we select. -- cgit v1.2.3 From 4a557d7e2d43055e0958e6d655cc72c38dc3787d Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 5 May 2009 12:49:16 -0400 Subject: Table constraint Ur code --- doc/manual.tex | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 3 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 368e3024..34972002 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1138,14 +1138,19 @@ $$\begin{array}{l} \mt{type} \; \mt{float} \\ \mt{type} \; \mt{string} \\ \mt{type} \; \mt{time} \\ + \mt{type} \; \mt{blob} \\ \\ \mt{type} \; \mt{unit} = \{\} \\ \\ \mt{datatype} \; \mt{bool} = \mt{False} \mid \mt{True} \\ \\ - \mt{datatype} \; \mt{option} \; \mt{t} = \mt{None} \mid \mt{Some} \; \mt{of} \; \mt{t} + \mt{datatype} \; \mt{option} \; \mt{t} = \mt{None} \mid \mt{Some} \; \mt{of} \; \mt{t} \\ + \\ + \mt{datatype} \; \mt{list} \; \mt{t} = \mt{Nil} \mid \mt{Cons} \; \mt{of} \; \mt{t} \times \mt{list} \; \mt{t} \end{array}$$ +The only unusual element of this list is the $\mt{blob}$ type, which stands for binary sequences. + Another important generic Ur element comes at the beginning of \texttt{top.urs}. $$\begin{array}{l} @@ -1203,10 +1208,109 @@ $$\begin{array}{l} The fundamental unit of interest in the embedding of SQL is tables, described by a type family and creatable only via the $\mt{table}$ declaration form. $$\begin{array}{l} - \mt{con} \; \mt{sql\_table} :: \{\mt{Type}\} \to \mt{Type} + \mt{con} \; \mt{sql\_table} :: \{\mt{Type}\} \to \{\{\mt{Unit}\}\} \to \mt{Type} +\end{array}$$ +The first argument to this constructor gives the names and types of a table's columns, and the second argument gives the set of valid keys. Keys are the only subsets of the columns that may be referenced as foreign keys. Each key has a name. + +We also have the simpler type family of SQL views, which have no keys. +$$\begin{array}{l} + \mt{con} \; \mt{sql\_view} :: \{\mt{Type}\} \to \mt{Type} \end{array}$$ -\subsubsection{\label{tables}Tables} +A multi-parameter type class is used to allow tables and views to be used interchangeably, with a way of extracting the set of columns from each. +$$\begin{array}{l} + \mt{class} \; \mt{fieldsOf} :: \mt{Type} \to \{\mt{Type}\} \to \mt{Type} \\ + \mt{val} \; \mt{fieldsOf\_table} : \mt{fs} ::: \{\mt{Type}\} \to \mt{keys} ::: \{\{\mt{Unit}\}\} \to \mt{fieldsOf} \; (\mt{sql\_table} \; \mt{fs} \; \mt{keys}) \; \mt{fs} \\ + \mt{val} \; \mt{fieldsOf\_view} : \mt{fs} ::: \{\mt{Type}\} \to \mt{fieldsOf} \; (\mt{sql\_view} \; \mt{fs}) \; \mt{fs} +\end{array}$$ + +\subsubsection{Table Constraints} + +Tables may be declared with constraints, such that database modifications that violate the constraints are blocked. A table may have at most one \texttt{PRIMARY KEY} constraint, which gives the subset of columns that will most often be used to look up individual rows in the table. + +$$\begin{array}{l} + \mt{con} \; \mt{primary\_key} :: \{\mt{Type}\} \to \{\{\mt{Unit}\}\} \to \mt{Type} \\ + \mt{val} \; \mt{no\_primary\_key} : \mt{fs} ::: \{\mt{Type}\} \to \mt{primary\_key} \; \mt{fs} \; [] \\ + \mt{val} \; \mt{primary\_key} : \mt{rest} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \to \mt{key1} :: \mt{Name} \to \mt{keys} :: \{\mt{Type}\} \\ + \hspace{.1in} \to [[\mt{key1}] \sim \mt{keys}] \Rightarrow [[\mt{key1} = \mt{t}] \rc \mt{keys} \sim \mt{rest}] \\ + \hspace{.1in} \Rightarrow \$([\mt{key1} = \mt{sql\_injectable\_prim} \; \mt{t}] \rc \mt{map} \; \mt{sql\_injectable\_prim} \; \mt{keys}) \\ + \hspace{.1in} \to \mt{primary\_key} \; ([\mt{key1} = \mt{t}] \rc \mt{keys} \rc \mt{rest}) \; [\mt{Pkey} = [\mt{key1}] \rc \mt{map} \; (\lambda \_ \Rightarrow ()) \; \mt{keys}] +\end{array}$$ +The type class $\mt{sql\_injectable\_prim}$ characterizes which types are allowed in SQL and are not $\mt{option}$ types. In SQL, a \texttt{PRIMARY KEY} constraint enforces after-the-fact that a column may not contain \texttt{NULL}s, but Ur/Web forces that information to be included in table types from the beginning. Thus, the only effect of this kind of constraint in Ur/Web is to enforce uniqueness of the given key within the table. + +A type family stands for sets of named constraints of the remaining varieties. +$$\begin{array}{l} + \mt{con} \; \mt{sql\_constraints} :: \{\mt{Type}\} \to \{\{\mt{Unit}\}\} \to \mt{Type} +\end{array}$$ +The first argument gives the column types of the table being constrained, and the second argument maps constraint names to the keys that they define. Constraints that don't define keys are mapped to ``empty keys.'' + +There is a type family of individual, unnamed constraints. +$$\begin{array}{l} + \mt{con} \; \mt{sql\_constraint} :: \{\mt{Type}\} \to \{\mt{Unit}\} \to \mt{Type} +\end{array}$$ +The first argument is the same as above, and the second argument gives the key columns for just this constraint. + +We have operations for assembling constraints into constraint sets. +$$\begin{array}{l} + \mt{val} \; \mt{no\_constraint} : \mt{fs} ::: \{\mt{Type}\} \to \mt{sql\_constraints} \; \mt{fs} \; [] \\ + \mt{val} \; \mt{one\_constraint} : \mt{fs} ::: \{\mt{Type}\} \to \mt{unique} ::: \{\mt{Unit}\} \to \mt{name} :: \mt{Name} \\ + \hspace{.1in} \to \mt{sql\_constraint} \; \mt{fs} \; \mt{unique} \to \mt{sql\_constraints} \; \mt{fs} \; [\mt{name} = \mt{unique}] \\ + \mt{val} \; \mt{join\_constraints} : \mt{fs} ::: \{\mt{Type}\} \to \mt{uniques1} ::: \{\{\mt{Unit}\}\} \to \mt{uniques2} ::: \{\{\mt{Unit}\}\} \to [\mt{uniques1} \sim \mt{uniques2}] \\ + \hspace{.1in} \Rightarrow \mt{sql\_constraints} \; \mt{fs} \; \mt{uniques1} \to \mt{sql\_constraints} \; \mt{fs} \; \mt{uniques2} \to \mt{sql\_constraints} \; \mt{fs} \; (\mt{uniques1} \rc \mt{uniques2}) +\end{array}$$ + +A \texttt{UNIQUE} constraint forces a set of columns to be a key, which means that no combination of column values may occur more than once in the table. The $\mt{unique1}$ and $\mt{unique}$ arguments are separated out only to ensure that empty \texttt{UNIQUE} constraints are rejected. +$$\begin{array}{l} + \mt{val} \; \mt{unique} : \mt{rest} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \to \mt{unique1} :: \mt{Name} \to \mt{unique} :: \{\mt{Type}\} \\ + \hspace{.1in} \to [[\mt{unique1}] \sim \mt{unique}] \Rightarrow [[\mt{unique1} = \mt{t}] \rc \mt{unique} \sim \mt{rest}] \\ + \hspace{.1in} \Rightarrow \mt{sql\_constraint} \; ([\mt{unique1} = \mt{t}] \rc \mt{unique} \rc \mt{rest}) \; ([\mt{unique1}] \rc \mt{map} \; (\lambda \_ \Rightarrow ()) \; \mt{unique}) +\end{array}$$ + +A \texttt{FOREIGN KEY} constraint connects a set of local columns to a local or remote key, enforcing that the local columns always reference an existent row of the foreign key's table. A local column of type $\mt{t}$ may be linked to a foreign column of type $\mt{option} \; \mt{t}$, and vice versa. We formalize that notion with a type class. +$$\begin{array}{l} + \mt{class} \; \mt{linkable} :: \mt{Type} \to \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{linkable\_same} : \mt{t} ::: \mt{Type} \to \mt{linkable} \; \mt{t} \; \mt{t} \\ + \mt{val} \; \mt{linkable\_from\_nullable} : \mt{t} ::: \mt{Type} \to \mt{linkable} \; (\mt{option} \; \mt{t}) \; \mt{t} \\ + \mt{val} \; \mt{linkable\_to\_nullable} : \mt{t} ::: \mt{Type} \to \mt{linkable} \; \mt{t} \; (\mt{option} \; \mt{t}) +\end{array}$$ + +The $\mt{matching}$ type family uses $\mt{linkable}$ to define when two keys match up type-wise. +$$\begin{array}{l} + \mt{con} \; \mt{matching} :: \{\mt{Type}\} \to \{\mt{Type}\} \to \mt{Type} \\ + \mt{val} \; \mt{mat\_nil} : \mt{matching} \; [] \; [] \\ + \mt{val} \; \mt{mat\_cons} : \mt{t1} ::: \mt{Type} \to \mt{rest1} ::: \{\mt{Type}\} \to \mt{t2} ::: \mt{Type} \to \mt{rest2} ::: \{\mt{Type}\} \to \mt{nm1} :: \mt{Name} \to \mt{nm2} :: \mt{Name} \\ + \hspace{.1in} \to [[\mt{nm1}] \sim \mt{rest1}] \Rightarrow [[\mt{nm2}] \sim \mt{rest2}] \Rightarrow \mt{linkable} \; \mt{t1} \; \mt{t2} \to \mt{matching} \; \mt{rest1} \; \mt{rest2} \\ + \hspace{.1in} \to \mt{matching} \; ([\mt{nm1} = \mt{t1}] \rc \mt{rest1}) \; ([\mt{nm2} = \mt{t2}] \rc \mt{rest2}) +\end{array}$$ + +SQL provides a number of different propagation modes for \texttt{FOREIGN KEY} constraints, governing what happens when a row containing a still-referenced foreign key value is deleted or modified to have a different key value. The argument of a propagation mode's type gives the local key type. +$$\begin{array}{l} + \mt{con} \; \mt{propagation\_mode} :: \{\mt{Type}\} \to \mt{Type} \\ + \mt{val} \; \mt{restrict} : \mt{fs} ::: \{\mt{Type}\} \to \mt{propagation\_mode} \; \mt{fs} \\ + \mt{val} \; \mt{cascade} : \mt{fs} ::: \{\mt{Type}\} \to \mt{propagation\_mode} \; \mt{fs} \\ + \mt{val} \; \mt{no\_action} : \mt{fs} ::: \{\mt{Type}\} \to \mt{propagation\_mode} \; \mt{fs} \\ + \mt{val} \; \mt{set\_null} : \mt{fs} ::: \{\mt{Type}\} \to \mt{propagation\_mode} \; (\mt{map} \; \mt{option} \; \mt{fs}) +\end{array}$$ + +Finally, we put these ingredient together to define the \texttt{FOREIGN KEY} constraint function. +$$\begin{array}{l} + \mt{val} \; \mt{foreign\_key} : \mt{mine1} ::: \mt{Name} \to \mt{t} ::: \mt{Type} \to \mt{mine} ::: \{\mt{Type}\} \to \mt{munused} ::: \{\mt{Type}\} \to \mt{foreign} ::: \{\mt{Type}\} \\ + \hspace{.1in} \to \mt{funused} ::: \{\mt{Type}\} \to \mt{nm} ::: \mt{Name} \to \mt{uniques} ::: \{\{\mt{Unit}\}\} \\ + \hspace{.1in} \to [[\mt{mine1}] \sim \mt{mine}] \Rightarrow [[\mt{mine1} = \mt{t}] \rc \mt{mine} \sim \mt{munused}] \Rightarrow [\mt{foreign} \sim \mt{funused}] \Rightarrow [[\mt{nm}] \sim \mt{uniques}] \\ + \hspace{.1in} \Rightarrow \mt{matching} \; ([\mt{mine1} = \mt{t}] \rc \mt{mine}) \; \mt{foreign} \\ + \hspace{.1in} \to \mt{sql\_table} \; (\mt{foreign} \rc \mt{funused}) \; ([\mt{nm} = \mt{map} \; (\lambda \_ \Rightarrow ()) \; \mt{foreign}] \rc \mt{uniques}) \\ + \hspace{.1in} \to \{\mt{OnDelete} : \mt{propagation\_mode} \; ([\mt{mine1} = \mt{t}] \rc \mt{mine}), \\ + \hspace{.2in} \mt{OnUpdate} : \mt{propagation\_mode} \; ([\mt{mine1} = \mt{t}] \rc \mt{mine})\} \\ + \hspace{.1in} \to \mt{sql\_constraint} \; ([\mt{mine1} = \mt{t}] \rc \mt{mine} \rc \mt{munused}) \; [] +\end{array}$$ + +The last kind of constraint is a \texttt{CHECK} constraint, which attaches a boolean invariant over a row's contents. It is defined using the $\mt{sql\_exp}$ type family, which we discuss in more detail below. +$$\begin{array}{l} + \mt{val} \; \mt{check} : \mt{fs} ::: \{\mt{Type}\} \to \mt{sql\_exp} \; [] \; [] \; \mt{fs} \; \mt{bool} \to \mt{sql\_constraint} \; \mt{fs} \; [] +\end{array}$$ + +Section \ref{tables} shows the expanded syntax of the $\mt{table}$ declaration and signature item that includes constraints. There is no other way to use constraints with SQL in Ur/Web. + \subsubsection{Queries} -- cgit v1.2.3 From fa9cab290144d669460ddbf20eb7dc079421f143 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 5 May 2009 13:21:26 -0400 Subject: Revised query types --- doc/manual.tex | 92 +++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 17 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 34972002..7ecbfafd 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1197,11 +1197,35 @@ $$\begin{array}{l} There are transactions for reading an HTTP header by name and for getting and setting strongly-typed cookies. Cookies may only be created by the $\mt{cookie}$ declaration form, ensuring that they be named consistently based on module structure. $$\begin{array}{l} -\mt{val} \; \mt{requestHeader} : \mt{string} \to \mt{transaction} \; (\mt{option} \; \mt{string}) \\ -\\ -\mt{con} \; \mt{http\_cookie} :: \mt{Type} \to \mt{Type} \\ -\mt{val} \; \mt{getCookie} : \mt{t} ::: \mt{Type} \to \mt{http\_cookie} \; \mt{t} \to \mt{transaction} \; (\mt{option} \; \mt{t}) \\ -\mt{val} \; \mt{setCookie} : \mt{t} ::: \mt{Type} \to \mt{http\_cookie} \; \mt{t} \to \mt{t} \to \mt{transaction} \; \mt{unit} + \mt{val} \; \mt{requestHeader} : \mt{string} \to \mt{transaction} \; (\mt{option} \; \mt{string}) \\ + \\ + \mt{con} \; \mt{http\_cookie} :: \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{getCookie} : \mt{t} ::: \mt{Type} \to \mt{http\_cookie} \; \mt{t} \to \mt{transaction} \; (\mt{option} \; \mt{t}) \\ + \mt{val} \; \mt{setCookie} : \mt{t} ::: \mt{Type} \to \mt{http\_cookie} \; \mt{t} \to \mt{t} \to \mt{transaction} \; \mt{unit} +\end{array}$$ + +There are also an abstract $\mt{url}$ type and functions for converting to it, based on the policy defined by \texttt{[allow|deny] url} directives in the project file. +$$\begin{array}{l} + \mt{type} \; \mt{url} \\ + \mt{val} \; \mt{bless} : \mt{string} \to \mt{url} \\ + \mt{val} \; \mt{checkUrl} : \mt{string} \to \mt{option} \; \mt{url} +\end{array}$$ +$\mt{bless}$ raises a runtime error if the string passed to it fails the URL policy. + +It's possible for pages to return files of arbitrary MIME types. A file can be input from the user using this data type, along with the $\mt{upload}$ form tag. +$$\begin{array}{l} + \mt{type} \; \mt{file} \\ + \mt{val} \; \mt{fileName} : \mt{file} \to \mt{option} \; \mt{string} \\ + \mt{val} \; \mt{fileMimeType} : \mt{file} \to \mt{string} \\ + \mt{val} \; \mt{fileData} : \mt{file} \to \mt{blob} +\end{array}$$ + +A blob can be extracted from a file and returned as the page result. There are bless and check functions for MIME types analogous to those for URLs. +$$\begin{array}{l} + \mt{type} \; \mt{mimeType} \\ + \mt{val} \; \mt{blessMime} : \mt{string} \to \mt{mimeType} \\ + \mt{val} \; \mt{checkMime} : \mt{string} \to \mt{option} \; \mt{mimeType} \\ + \mt{val} \; \mt{returnBlob} : \mt{t} ::: \mt{Type} \to \mt{blob} \to \mt{mimeType} \to \mt{transaction} \; \mt{t} \end{array}$$ \subsection{SQL} @@ -1358,7 +1382,7 @@ $$\begin{array}{l} \hspace{.1in} \to \mt{grouped} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedFields} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedExps} ::: \{\mt{Type}\} \\ - \hspace{.1in} \to \{\mt{From} : \$(\mt{map} \; \mt{sql\_table} \; \mt{tables}), \\ + \hspace{.1in} \to \{\mt{From} : \mt{sql\_from\_items} \; \mt{tables}, \\ \hspace{.2in} \mt{Where} : \mt{sql\_exp} \; \mt{tables} \; [] \; [] \; \mt{bool}, \\ \hspace{.2in} \mt{GroupBy} : \mt{sql\_subset} \; \mt{tables} \; \mt{grouped}, \\ \hspace{.2in} \mt{Having} : \mt{sql\_exp} \; \mt{grouped} \; \mt{tables} \; [] \; \mt{bool}, \\ @@ -1399,17 +1423,20 @@ $$\begin{array}{l} Ur values of appropriate types may be injected into SQL expressions. $$\begin{array}{l} + \mt{class} \; \mt{sql\_injectable\_prim} \\ + \mt{val} \; \mt{sql\_bool} : \mt{sql\_injectable\_prim} \; \mt{bool} \\ + \mt{val} \; \mt{sql\_int} : \mt{sql\_injectable\_prim} \; \mt{int} \\ + \mt{val} \; \mt{sql\_float} : \mt{sql\_injectable\_prim} \; \mt{float} \\ + \mt{val} \; \mt{sql\_string} : \mt{sql\_injectable\_prim} \; \mt{string} \\ + \mt{val} \; \mt{sql\_time} : \mt{sql\_injectable\_prim} \; \mt{time} \\ + \mt{val} \; \mt{sql\_blob} : \mt{sql\_injectable\_prim} \; \mt{blob} \\ + \mt{val} \; \mt{sql\_channel} : \mt{t} ::: \mt{Type} \to \mt{sql\_injectable\_prim} \; (\mt{channel} \; \mt{t}) \\ + \mt{val} \; \mt{sql\_client} : \mt{sql\_injectable\_prim} \; \mt{client} \\ + \\ \mt{class} \; \mt{sql\_injectable} \\ - \mt{val} \; \mt{sql\_bool} : \mt{sql\_injectable} \; \mt{bool} \\ - \mt{val} \; \mt{sql\_int} : \mt{sql\_injectable} \; \mt{int} \\ - \mt{val} \; \mt{sql\_float} : \mt{sql\_injectable} \; \mt{float} \\ - \mt{val} \; \mt{sql\_string} : \mt{sql\_injectable} \; \mt{string} \\ - \mt{val} \; \mt{sql\_time} : \mt{sql\_injectable} \; \mt{time} \\ - \mt{val} \; \mt{sql\_option\_bool} : \mt{sql\_injectable} \; (\mt{option} \; \mt{bool}) \\ - \mt{val} \; \mt{sql\_option\_int} : \mt{sql\_injectable} \; (\mt{option} \; \mt{int}) \\ - \mt{val} \; \mt{sql\_option\_float} : \mt{sql\_injectable} \; (\mt{option} \; \mt{float}) \\ - \mt{val} \; \mt{sql\_option\_string} : \mt{sql\_injectable} \; (\mt{option} \; \mt{string}) \\ - \mt{val} \; \mt{sql\_option\_time} : \mt{sql\_injectable} \; (\mt{option} \; \mt{time}) \\ + \mt{val} \; \mt{sql\_prim} : \mt{t} ::: \mt{Type} \to \mt{sql\_injectable\_prim} \; \mt{t} \to \mt{sql\_injectable} \; \mt{t} \\ + \mt{val} \; \mt{sql\_option\_prim} : \mt{t} ::: \mt{Type} \to \mt{sql\_injectable\_prim} \; \mt{t} \to \mt{sql\_injectable} \; (\mt{option} \; \mt{t}) \\ + \\ \mt{val} \; \mt{sql\_inject} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \to \mt{sql\_injectable} \; \mt{t} \\ \hspace{.1in} \to \mt{t} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{t} \end{array}$$ @@ -1455,7 +1482,6 @@ $$\begin{array}{l} \end{array}$$ Finally, we have aggregate functions. The $\mt{COUNT(\ast)}$ syntax is handled specially, since it takes no real argument. The other aggregate functions are placed into a general type family, using constructor classes to restrict usage to properly-typed arguments. The key aspect of the $\mt{sql\_aggregate}$ function's type is the shift of aggregate-function-only fields into unrestricted fields. - $$\begin{array}{l} \mt{val} \; \mt{sql\_count} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{int} \end{array}$$ @@ -1484,6 +1510,36 @@ $$\begin{array}{l} \mt{val} \; \mt{sql\_min} : \mt{t} ::: \mt{Type} \to \mt{sql\_maxable} \; \mt{t} \to \mt{sql\_aggregate} \; \mt{t} \end{array}$$ +\texttt{FROM} clauses are specified using a type family. +$$\begin{array}{l} + \mt{con} \; \mt{sql\_from\_items} :: \{\{\mt{Type}\}\} \to \mt{Type} \\ + \mt{val} \; \mt{sql\_from\_table} : \mt{t} ::: \mt{Type} \to \mt{fs} ::: \{\mt{Type}\} \to \mt{fieldsOf} \; \mt{t} \; \mt{fs} \to \mt{name} :: \mt{Name} \to \mt{t} \to \mt{sql\_from\_items} \; [\mt{name} = \mt{fs}] \\ + \mt{val} \; \mt{sql\_from\_comma} : \mt{tabs1} ::: \{\{\mt{Type}\}\} \to \mt{tabs2} ::: \{\{\mt{Type}\}\} \to [\mt{tabs1} \sim \mt{tabs2}] \\ + \hspace{.1in} \Rightarrow \mt{sql\_from\_items} \; \mt{tabs1} \to \mt{sql\_from\_items} \; \mt{tabs2} \\ + \hspace{.1in} \to \mt{sql\_from\_items} \; (\mt{tabs1} \rc \mt{tabs2}) \\ + \mt{val} \; \mt{sql\_inner\_join} : \mt{tabs1} ::: \{\{\mt{Type}\}\} \to \mt{tabs2} ::: \{\{\mt{Type}\}\} \to [\mt{tabs1} \sim \mt{tabs2}] \\ + \hspace{.1in} \Rightarrow \mt{sql\_from\_items} \; \mt{tabs1} \to \mt{sql\_from\_items} \; \mt{tabs2} \\ + \hspace{.1in} \to \mt{sql\_exp} \; (\mt{tabs1} \rc \mt{tabs2}) \; [] \; [] \; \mt{bool} \\ + \hspace{.1in} \to \mt{sql\_from\_items} \; (\mt{tabs1} \rc \mt{tabs2}) +\end{array}$$ + +Besides these basic cases, outer joins are supported, which requires a type class for turning non-$\mt{option}$ columns into $\mt{option}$ columns. +$$\begin{array}{l} + \mt{class} \; \mt{nullify} :: \mt{Type} \to \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{nullify\_option} : \mt{t} ::: \mt{Type} \to \mt{nullify} \; (\mt{option} \; \mt{t}) \; (\mt{option} \; \mt{t}) \\ + \mt{val} \; \mt{nullify\_prim} : \mt{t} ::: \mt{Type} \to \mt{sql\_injectable\_prim} \; \mt{t} \to \mt{nullify} \; \mt{t} \; (\mt{option} \; \mt{t}) +\end{array}$$ + +Left, right, and full outer joins can now be expressed using functions that accept records of $\mt{nullify}$ instances. Here, we give only the type for a left join as an example. + +$$\begin{array}{l} + \mt{val} \; \mt{sql\_left\_join} : \mt{tabs1} ::: \{\{\mt{Type}\}\} \to \mt{tabs2} ::: \{\{(\mt{Type} \times \mt{Type})\}\} \to [\mt{tabs1} \sim \mt{tabs2}] \\ + \hspace{.1in} \Rightarrow \$(\mt{map} \; (\lambda \mt{r} \Rightarrow \$(\mt{map} \; (\lambda \mt{p} :: (\mt{Type} \times \mt{Type}) \Rightarrow \mt{nullify} \; \mt{p}.1 \; \mt{p}.2) \; \mt{r})) \; \mt{tabs2}) \\ + \hspace{.1in} \to \mt{sql\_from\_items} \; \mt{tabs1} \to \mt{sql\_from\_items} \; (\mt{map} \; (\mt{map} \; (\lambda \mt{p} :: (\mt{Type} \times \mt{Type}) \Rightarrow \mt{p}.1)) \; \mt{tabs2}) \\ + \hspace{.1in} \to \mt{sql\_exp} \; (\mt{tabs1} \rc \mt{map} \; (\mt{map} \; (\lambda \mt{p} :: (\mt{Type} \times \mt{Type}) \Rightarrow \mt{p}.1)) \; \mt{tabs2}) \; [] \; [] \; \mt{bool} \\ + \hspace{.1in} \to \mt{sql\_from\_items} \; (\mt{tabs1} \rc \mt{map} \; (\mt{map} \; (\lambda \mt{p} :: (\mt{Type} \times \mt{Type}) \Rightarrow \mt{p}.2)) \; \mt{tabs2}) +\end{array}$$ + We wrap up the definition of query syntax with the types used in representing $\mt{ORDER} \; \mt{BY}$, $\mt{LIMIT}$, and $\mt{OFFSET}$ clauses. $$\begin{array}{l} \mt{type} \; \mt{sql\_direction} \\ @@ -1669,6 +1725,8 @@ Ur/Web features some syntactic shorthands for building values using the function \subsection{SQL} +\subsubsection{\label{tables}Table Declarations} + \subsubsection{Queries} Queries $Q$ are added to the rules for expressions $e$. -- cgit v1.2.3 From 2daada8672b843c596d110d556cb4d8b136dea85 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 5 May 2009 14:15:55 -0400 Subject: on* handlers --- doc/manual.tex | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 7ecbfafd..9acbc1c5 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1621,10 +1621,13 @@ There is a function for producing an XML tree with a particular tag at its root. $$\begin{array}{l} \mt{val} \; \mt{tag} : \mt{attrsGiven} ::: \{\mt{Type}\} \to \mt{attrsAbsent} ::: \{\mt{Type}\} \to \mt{ctxOuter} ::: \{\mt{Unit}\} \to \mt{ctxInner} ::: \{\mt{Unit}\} \\ \hspace{.1in} \to \mt{useOuter} ::: \{\mt{Type}\} \to \mt{useInner} ::: \{\mt{Type}\} \to \mt{bindOuter} ::: \{\mt{Type}\} \to \mt{bindInner} ::: \{\mt{Type}\} \\ - \hspace{.1in} \to \lambda [\mt{attrsGiven} \sim \mt{attrsAbsent}] \; [\mt{useOuter} \sim \mt{useInner}] \; [\mt{bindOuter} \sim \mt{bindInner}] \Rightarrow \$\mt{attrsGiven} \\ + \hspace{.1in} \to \lambda [\mt{attrsGiven} \sim \mt{attrsAbsent}] \; [\mt{useOuter} \sim \mt{useInner}] \; [\mt{bindOuter} \sim \mt{bindInner}] \\ + \hspace{.1in} \Rightarrow \mt{option} \; \mt{css\_class} \\ + \hspace{.1in} \to \$\mt{attrsGiven} \\ \hspace{.1in} \to \mt{tag} \; (\mt{attrsGiven} \rc \mt{attrsAbsent}) \; \mt{ctxOuter} \; \mt{ctxInner} \; \mt{useOuter} \; \mt{bindOuter} \\ \hspace{.1in} \to \mt{xml} \; \mt{ctxInner} \; \mt{useInner} \; \mt{bindInner} \to \mt{xml} \; \mt{ctxOuter} \; (\mt{useOuter} \rc \mt{useInner}) \; (\mt{bindOuter} \rc \mt{bindInner}) \end{array}$$ +Note that any tag may be assigned a CSS class. This is the sole way of making use of the values produced by $\mt{style}$ declarations. Ur/Web itself doesn't deal with the syntax or semantics of style sheets; they can be linked via URLs with \texttt{link} tags. However, Ur/Web does make it easy to calculate upper bounds on usage of CSS classes through program analysis. Two XML fragments may be concatenated. $$\begin{array}{l} @@ -1668,6 +1671,15 @@ $$\begin{array}{l} \mt{val} \; \mt{sleep} : \mt{int} \to \mt{transaction} \; \mt{unit} \end{array}$$ +A few functions are available to registers callbacks for particular error events. Respectively, they are triggered on calls to $\mt{error}$, uncaught JavaScript exceptions, failure of remote procedure calls, the severance of the connection serving asynchronous messages, or the occurrence of some other error with that connection. If no handlers are registered for a kind of error, then occurrences of that error are ignored silently. +$$\begin{array}{l} + \mt{val} \; \mt{onError} : (\mt{xbody} \to \mt{transaction} \; \mt{unit}) \to \mt{transaction} \; \mt{unit} \\ + \mt{val} \; \mt{onFail} : (\mt{string} \to \mt{transaction} \; \mt{unit}) \to \mt{transaction} \; \mt{unit} \\ + \mt{val} \; \mt{onConnectFail} : \mt{transaction} \; \mt{unit} \to \mt{transaction} \; \mt{unit} \\ + \mt{val} \; \mt{onDisconnect} : \mt{transaction} \; \mt{unit} \to \mt{transaction} \; \mt{unit} \\ + \mt{val} \; \mt{onServerError} : (\mt{string} \to \mt{transaction} \; \mt{unit}) \to \mt{transaction} \; \mt{unit} +\end{array}$$ + \subsubsection{Functional-Reactive Page Generation} Most approaches to ``AJAX''-style coding involve imperative manipulation of the DOM tree representing an HTML document's structure. Ur/Web follows the \emph{functional-reactive} approach instead. Programs may allocate mutable \emph{sources} of arbitrary types, and an HTML page is effectively a pure function over the latest values of the sources. The page is not mutated directly, but rather it changes automatically as the sources are mutated. -- cgit v1.2.3 From 331a548a72381bb798611ac8cd5e8397699bf17d Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 5 May 2009 14:36:16 -0400 Subject: Constraint syntax --- doc/manual.tex | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 9acbc1c5..b822b7a2 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1739,6 +1739,21 @@ Ur/Web features some syntactic shorthands for building values using the function \subsubsection{\label{tables}Table Declarations} +$\mt{table}$ declarations may include constraints, via these grammar rules. +$$\begin{array}{rrcll} + \textrm{Declarations} & d &::=& \mt{table} \; x : c \; [pk[,]] \; cts \\ + \textrm{Primary key constraints} & pk &::=& \mt{PRIMARY} \; \mt{KEY} \; K \\ + \textrm{Keys} & K &::=& f \mid (f, (f,)^+) \\ + \textrm{Constraint sets} & cts &::=& \mt{CONSTRAINT} f \; ct \mid cts, cts \mid \{\{e\}\} \\ + \textrm{Constraints} & ct &::=& \mt{UNIQUE} \; K \mid \mt{CHECK} \; E \\ + &&& \mid \mt{FOREIGN} \; \mt{KEY} \; K \; \mt{REFERENCES} \; F \; (K) \; [\mt{ON} \; \mt{DELETE} \; pr] \; [\mt{ON} \; \mt{UPDATE} \; pr] \\ + \textrm{Foreign tables} & F &::=& x \mid \{\{e\}\} \\ + \textrm{Propagation modes} & pr &::=& \mt{NO} \; \mt{ACTION} \mid \mt{RESTRICT} \mid \mt{CASCADE} \mid \mt{SET} \; \mt{NULL} +\end{array}$$ + +A signature item $\mt{table} \; \mt{x} : \mt{c}$ is actually elaborated into two signature items: $\mt{con} \; \mt{x\_hidden\_constraints} :: \{\{\mt{Unit}\}\}$ and $\mt{val} \; \mt{x} : \mt{sql\_table} \; \mt{c} \; \mt{x\_hidden\_constraints}$. This is appropriate for common cases where client code doesn't care which keys a table has. It's also possible to include constraints after a $\mt{table}$ signature item, with the same syntax as for $\mt{table}$ declarations. This may look like dependent typing, but it's just a convenience. The constraints are type-checked to determine a constructor $u$ to include in $\mt{val} \; \mt{x} : \mt{sql\_table} \; \mt{c} \; (u \rc \mt{x\_hidden\_constraints})$, and then the expressions are thrown away. Nonetheless, it can be useful for documentation purposes to include table constraint details in signatures. Note that the automatic generation of $\mt{x\_hidden\_constraints}$ leads to a kind of free subtyping with respect to which constraints are defined. + + \subsubsection{Queries} Queries $Q$ are added to the rules for expressions $e$. -- cgit v1.2.3 From bb11d221d430a04da3214229cd6f1792f70b6cb8 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 5 May 2009 14:45:21 -0400 Subject: Describe GET/POST --- doc/manual.tex | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index b822b7a2..52403a7e 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1846,6 +1846,12 @@ For both links and actions, direct arguments and local variables mentioned impli Ur/Web programs generally mix server- and client-side code in a fairly transparent way. The one important restriction is that mixed client-server code must encapsulate all server-side pieces within named functions. This is because execution of such pieces will be implemented by explicit calls to the remote web server, and it is useful to get the programmer's help in designing the interface to be used. For example, this makes it easier to allow a client running an old version of an application to continue interacting with a server that has been upgraded to a new version, if the programmer took care to keep the interfaces of all of the old remote calls the same. The functions implementing these services are assigned names in the same way as normal web entry points, by using module structure. +\medskip + +The HTTP standard suggests that GET requests only be used in ways that generate no side effects. Side effecting operations should use POST requests instead. The Ur/Web compiler enforces this rule strictly, via a simple conservative program analysis. Any page that may have a side effect must be accessed through a form, all of which use POST requests. A page is judged to have a side effect if its code depends syntactically on any of the side-effecting, server-side FFI functions. Links, forms, and most client-side event handlers are not followed during this syntactic traversal, but \texttt{} handlers \emph{are} examined, since they run right away and could just as well be considered parts of main page handlers. + +Ur/Web includes a kind of automatic protection against cross site request forgery attacks. Whenever any page execution can have side effects and can also read at least one cookie value, all cookie values must be signed cryptographically, to ensure that the user has come to the current page by submitting a form on a real page generated by the proper server. Signing and signature checking are inserted automatically by the compiler. This prevents attacks like phishing schemes where users are directed to counterfeit pages with forms that submit to your application, where a user's cookies might be submitted without his knowledge, causing some undesired side effect. + \section{Compiler Phases} -- cgit v1.2.3 From c701f11b2ee105af75dbeb4baaf0f2c35bb417e2 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 23 Jun 2009 12:53:47 -0400 Subject: New release --- CHANGELOG | 13 +++++++++++++ LICENSE | 2 +- doc/manual.tex | 21 ++++++++++++++++----- 3 files changed, 30 insertions(+), 6 deletions(-) (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index bf072690..b9018446 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,16 @@ +======== +20090623 +======== + +- Many bug fixes +- Mutually-recursive datatypes +- SML-style pattern-matching syntax for "fun", "fn", and local "val" +- Backwards-incompatible change to syntax of formal constructor parameters to + value-level functions, to support the previous change +- Path map support inspired by SML/NJ CM and MLton ML Basis +- Start of some new standard library modules +- Some improvements to JavaScript runtime, including better error handling + ======== 20090505 ======== diff --git a/LICENSE b/LICENSE index 0c963687..0c3fa118 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2008, Adam Chlipala +Copyright (c) 2008-2009, Adam Chlipala All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/doc/manual.tex b/doc/manual.tex index 52403a7e..0964133e 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -140,6 +140,7 @@ Here is the complete list of directive forms. ``FFI'' stands for ``foreign func \item \texttt{jsFunc Module.ident=name} gives the JavaScript name of an FFI value. \item \texttt{library FILENAME} parses \texttt{FILENAME.urp} and merges its contents with the rest of the current file's contents. \item \texttt{link FILENAME} adds \texttt{FILENAME} to the list of files to be passed to the GCC linker at the end of compilation. This is most useful for importing extra libraries needed by new FFI modules. +\item \texttt{path NAME=VALUE} creates a mapping from \texttt{NAME} to \texttt{VALUE}. This mapping may be used at the beginnings of filesystem paths given to various other configuration directives. A path like \texttt{\$NAME/rest} is expanded to \texttt{VALUE/rest}. There is an initial mapping from the empty name (for paths like \texttt{\$/list}) to the directory where the Ur/Web standard library is installed. If you accept the default \texttt{configure} options, this directory is \texttt{/usr/local/lib/urweb/ur}. \item \texttt{prefix PREFIX} sets the prefix included before every URI within the generated application. The default is \texttt{/}. \item \texttt{profile} generates an executable that may be used with gprof. \item \texttt{rewrite KIND FROM TO} gives a rule for rewriting canonical module paths. For instance, the canonical path of a page may be \texttt{Mod1.Mod2.mypage}, while you would rather the page were accessed via a URL containing only \texttt{page}. The directive \texttt{rewrite url Mod1/Mod2/mypage page} would accomplish that. The possible values of \texttt{KIND} determine which kinds of objects are affected. The kind \texttt{all} matches any object, and \texttt{url} matches page URLs. The kinds \texttt{table}, \texttt{sequence}, and \texttt{view} match those sorts of SQL entities, and \texttt{relation} matches any of those three. \texttt{cookie} matches HTTP cookies, and \texttt{style} matches CSS class names. If \texttt{FROM} ends in \texttt{/*}, it is interpreted as a prefix matching rule, and rewriting occurs by replacing only the appropriate prefix of a path with \texttt{TO}. While the actual external names of relations and styles have parts separated by underscores instead of slashes, all rewrite rules must be written in terms of slashes. @@ -288,6 +289,7 @@ $$\begin{array}{rrcll} &&& \hat{X} \; p & \textrm{unary constructor} \\ &&& \{(x = p,)^*\} & \textrm{rigid record pattern} \\ &&& \{(x = p,)^+, \ldots\} & \textrm{flexible record pattern} \\ + &&& p : \tau & \textrm{type annotation} \\ &&& (p) & \textrm{explicit precedence} \\ \\ \textrm{Qualified capitalized variables} & \hat{X} &::=& X & \textrm{not from a module} \\ @@ -304,7 +306,7 @@ $$\begin{array}{rrcll} &&& e \; e & \textrm{function application} \\ &&& \lambda x : \tau \Rightarrow e & \textrm{function abstraction} \\ &&& e [c] & \textrm{polymorphic function application} \\ - &&& \lambda x \; ? \; \kappa \Rightarrow e & \textrm{polymorphic function abstraction} \\ + &&& \lambda [x \; ? \; \kappa] \Rightarrow e & \textrm{polymorphic function abstraction} \\ &&& e [\kappa] & \textrm{kind-polymorphic function application} \\ &&& X \Longrightarrow e & \textrm{kind-polymorphic function abstraction} \\ \\ @@ -372,7 +374,7 @@ The notation $[c_1, \ldots, c_n]$ is shorthand for $[c_1 = (), \ldots, c_n = ()] A tuple type $(\tau_1, \ldots, \tau_n)$ expands to a record type $\{1 = \tau_1, \ldots, n = \tau_n\}$, with natural numbers as field names. A tuple pattern $(p_1, \ldots, p_n)$ expands to a rigid record pattern $\{1 = p_1, \ldots, n = p_n\}$. Positive natural numbers may be used in most places where field names would be allowed. -In general, several adjacent $\lambda$ forms may be combined into one, and kind and type annotations may be omitted, in which case they are implicitly included as wildcards. More formally, for constructor-level abstractions, we can define a new non-terminal $b ::= x \mid (x :: \kappa) \mid X$ and allow composite abstractions of the form $\lambda b^+ \Rightarrow c$, elaborating into the obvious sequence of one core $\lambda$ per element of $b^+$. +In general, several adjacent $\lambda$ forms may be combined into one, and kind and type annotations may be omitted, in which case they are implicitly included as wildcards. More formally, for constructor-level abstractions, we can define a new non-terminal $b ::= x \mid (x :: \kappa) \mid X$ and allow composite abstractions of the form $\lambda b^+ \Rightarrow c$, elaborating into the obvious sequence of one core $\lambda$ per element of $b^+$. For any signature item or declaration that defines some entity to be equal to $A$ with classification annotation $B$ (e.g., $\mt{val} \; x : B = A$), $B$ and the preceding colon (or similar punctuation) may be omitted, in which case it is filled in as a wildcard. @@ -382,12 +384,16 @@ A signature item or declaration $\mt{class} \; x = \lambda y \Rightarrow c$ may Handling of implicit and explicit constructor arguments may be tweaked with some prefixes to variable references. An expression $@x$ is a version of $x$ where all implicit constructor arguments have been made explicit. An expression $@@x$ achieves the same effect, additionally halting automatic resolution of type class instances and automatic proving of disjointness constraints. The default is that any prefix of a variable's type consisting only of implicit polymorphism, type class instances, and disjointness obligations is resolved automatically, with the variable treated as having the type that starts after the last implicit element, with suitable unification variables substituted. The same syntax works for variables projected out of modules and for capitalized variables (datatype constructors). -At the expression level, an analogue is available of the composite $\lambda$ form for constructors. We define the language of binders as $b ::= x \mid (x : \tau) \mid (x \; ? \; \kappa) \mid X \mid [c \sim c]$. A lone variable $x$ as a binder stands for an expression variable of unspecified type. +At the expression level, an analogue is available of the composite $\lambda$ form for constructors. We define the language of binders as $b ::= p \mid [x] \mid [x \; ? \; \kappa] \mid X \mid [c \sim c]$. A lone variable $[x]$ stands for an implicit constructor variable of unspecified kind. The standard value-level function binder is recovered as the type-annotated pattern form $x : \tau$. It is a compile-time error to include a pattern $p$ that does not match every value of the appropriate type. -A $\mt{val}$ or $\mt{val} \; \mt{rec}$ declaration may include expression binders before the equal sign, following the binder grammar from the last paragraph. Such declarations are elaborated into versions that add additional $\lambda$s to the fronts of the righthand sides, as appropriate. The keyword $\mt{fun}$ is a synonym for $\mt{val} \; \mt{rec}$. +A local $\mt{val}$ declaration may bind a pattern instead of just a plain variable. As for function arguments, only irrefutable patterns are legal. + +The keyword $\mt{fun}$ is a shorthand for $\mt{val} \; \mt{rec}$ that allows arguments to be specified before the equal sign in the definition of each mutually-recursive function, as in SML. Each curried argument must follow the grammar of the $b$ non-terminal introduced two paragraphs ago. A $\mt{fun}$ declaration is elaborated into a version that adds additional $\lambda$s to the fronts of the righthand sides, as appropriate. A signature item $\mt{functor} \; X_1 \; (X_2 : S_1) : S_2$ is elaborated into $\mt{structure} \; X_1 : \mt{functor}(X_2 : S_1) : S_2$. A declaration $\mt{functor} \; X_1 \; (X_2 : S_1) : S_2 = M$ is elaborated into $\mt{structure} \; X_1 : \mt{functor}(X_2 : S_1) : S_2 = \mt{functor}(X_2 : S_1) : S_2 = M$. +An $\mt{open} \; \mt{constraints}$ declaration is implicitly inserted for the argument of every functor at the beginning of the functor body. For every declaration of the form $\mt{structure} \; X : S = \mt{struct} \ldots \mt{end}$, an $\mt{open} \; \mt{constraints} \; X$ declaration is implicitly inserted immediately afterward. + A declaration $\mt{table} \; x : \{(c = c,)^*\}$ is elaborated into $\mt{table} \; x : [(c = c,)^*]$ The syntax $\mt{where} \; \mt{type}$ is an alternate form of $\mt{where} \; \mt{con}$. @@ -644,7 +650,7 @@ $$\infer{\Gamma \vdash e [c] : [x \mapsto c]\tau}{ \Gamma \vdash e : x :: \kappa \to \tau & \Gamma \vdash c :: \kappa } -\quad \infer{\Gamma \vdash \lambda x \; ? \; \kappa \Rightarrow e : x \; ? \; \kappa \to \tau}{ +\quad \infer{\Gamma \vdash \lambda [x \; ? \; \kappa] \Rightarrow e : x \; ? \; \kappa \to \tau}{ \Gamma, x :: \kappa \vdash e : \tau }$$ @@ -732,6 +738,11 @@ $$\infer{\Gamma \vdash \{\overline{x = p}\} \leadsto \Gamma_n; \{\overline{x = \ & \forall i: \Gamma_i \vdash p_i \leadsto \Gamma_{i+1}; \tau_i }$$ +$$\infer{\Gamma \vdash p : \tau \leadsto \Gamma'; \tau}{ + \Gamma \vdash p \leadsto \Gamma'; \tau' + & \Gamma \vdash \tau' \equiv \tau +}$$ + \subsection{Declaration Typing} We use an auxiliary judgment $\overline{y}; x; \Gamma \vdash \overline{dc} \leadsto \Gamma'$, expressing the enrichment of $\Gamma$ with the types of the datatype constructors $\overline{dc}$, when they are known to belong to datatype $x$ with type parameters $\overline{y}$. -- cgit v1.2.3 From 3602f46fee1c01d173177298abd3caa58e3d946b Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 23 Jun 2009 14:05:12 -0400 Subject: Factor out common request functionality, in preparation for supporting different protocols --- Makefile.in | 9 +- doc/manual.tex | 2 +- include/request.h | 22 +++ include/types.h | 2 + src/c/driver.c | 481 +++--------------------------------------------------- src/c/request.c | 467 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/compiler.sml | 4 +- 7 files changed, 520 insertions(+), 467 deletions(-) create mode 100644 include/request.h create mode 100644 src/c/request.c (limited to 'doc') diff --git a/Makefile.in b/Makefile.in index 4f50ec81..9347e96f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -13,7 +13,7 @@ all: smlnj mlton c smlnj: src/urweb.cm mlton: bin/urweb -c: lib/c/urweb.o lib/c/driver.o +c: lib/c/urweb.o lib/c/request.o lib/c/driver.o clean: rm -f src/*.mlton.grm.* src/*.mlton.lex.* \ @@ -21,11 +21,8 @@ clean: lib/c/*.o rm -rf .cm src/.cm -lib/c/urweb.o: src/c/urweb.c include/*.h - gcc -O3 -I include -c src/c/urweb.c -o lib/c/urweb.o $(CFLAGS) - -lib/c/driver.o: src/c/driver.c include/*.h - gcc -O3 -I include -c src/c/driver.c -o lib/c/driver.o $(CFLAGS) +lib/c/%.o: src/c/%.c include/*.h + gcc -O3 -I include -c $< -o $@ $(CFLAGS) src/urweb.cm: src/prefix.cm src/sources cat src/prefix.cm src/sources \ diff --git a/doc/manual.tex b/doc/manual.tex index 0964133e..22c219ad 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -394,7 +394,7 @@ A signature item $\mt{functor} \; X_1 \; (X_2 : S_1) : S_2$ is elaborated into $ An $\mt{open} \; \mt{constraints}$ declaration is implicitly inserted for the argument of every functor at the beginning of the functor body. For every declaration of the form $\mt{structure} \; X : S = \mt{struct} \ldots \mt{end}$, an $\mt{open} \; \mt{constraints} \; X$ declaration is implicitly inserted immediately afterward. -A declaration $\mt{table} \; x : \{(c = c,)^*\}$ is elaborated into $\mt{table} \; x : [(c = c,)^*]$ +A declaration $\mt{table} \; x : \{(c = c,)^*\}$ is elaborated into $\mt{table} \; x : [(c = c,)^*]$. The syntax $\mt{where} \; \mt{type}$ is an alternate form of $\mt{where} \; \mt{con}$. diff --git a/include/request.h b/include/request.h new file mode 100644 index 00000000..1111f47f --- /dev/null +++ b/include/request.h @@ -0,0 +1,22 @@ +#ifndef REQUEST_H +#define REQUEST_H + +#include + +#include "types.h" + +typedef struct uw_rc *uw_request_context; + +void uw_request_init(void); +void uw_sign(const char *in, char *out); + +uw_request_context uw_new_request_context(void); +void uw_free_request_context(uw_request_context); + +request_result uw_request(uw_request_context, uw_context, char *request, size_t request_len, int sock); + +uw_context uw_request_new_context(void); + +void *client_pruner(void *data); + +#endif diff --git a/include/types.h b/include/types.h index ca9ef152..4a28452b 100644 --- a/include/types.h +++ b/include/types.h @@ -40,6 +40,8 @@ typedef struct uw_Basis_file { typedef enum { SUCCESS, FATAL, BOUNDED_RETRY, UNLIMITED_RETRY, RETURN_BLOB } failure_kind; +typedef enum { SERVED, KEEP_OPEN, FAILED } request_result; + typedef struct input *uw_input; #define INTS_MAX 50 diff --git a/src/c/driver.c b/src/c/driver.c index 2140cb2c..56eba9a6 100644 --- a/src/c/driver.c +++ b/src/c/driver.c @@ -14,9 +14,9 @@ #include #include "urweb.h" +#include "request.h" int uw_backlog = 10; -int uw_bufsize = 1024; typedef struct node { int fd; @@ -54,108 +54,16 @@ static int dequeue() { static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER; -#define MAX_RETRIES 5 - -static int try_rollback(uw_context ctx) { - int r = uw_rollback(ctx); - - if (r) { - printf("Error running SQL ROLLBACK\n"); - uw_reset(ctx); - uw_write(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); - uw_write(ctx, "Content-type: text/plain\r\n\r\n"); - uw_write(ctx, "Error running SQL ROLLBACK\n"); - } - - return r; -} - -static uw_context new_context() { - uw_context ctx = uw_init(); - int retries_left = MAX_RETRIES; - - while (1) { - failure_kind fk = uw_begin_init(ctx); - - if (fk == SUCCESS) { - printf("Database connection initialized.\n"); - break; - } else if (fk == BOUNDED_RETRY) { - if (retries_left) { - printf("Initialization error triggers bounded retry: %s\n", uw_error_message(ctx)); - --retries_left; - } else { - printf("Fatal initialization error (out of retries): %s\n", uw_error_message(ctx)); - uw_free(ctx); - return NULL; - } - } else if (fk == UNLIMITED_RETRY) - printf("Initialization error triggers unlimited retry: %s\n", uw_error_message(ctx)); - else if (fk == FATAL) { - printf("Fatal initialization error: %s\n", uw_error_message(ctx)); - uw_free(ctx); - return NULL; - } else { - printf("Unknown uw_begin_init return code!\n"); - uw_free(ctx); - return NULL; - } - } - - return ctx; -} - -#define KEYSIZE 16 -#define PASSSIZE 4 - -#define HASH_ALGORITHM MHASH_SHA256 -#define HASH_BLOCKSIZE 32 -#define KEYGEN_ALGORITHM KEYGEN_MCRYPT - -int uw_hash_blocksize = HASH_BLOCKSIZE; - -static int password[PASSSIZE]; -static unsigned char private_key[KEYSIZE]; - -static void init_crypto() { - KEYGEN kg = {{HASH_ALGORITHM, HASH_ALGORITHM}}; - int i; - - assert(mhash_get_block_size(HASH_ALGORITHM) == HASH_BLOCKSIZE); - - for (i = 0; i < PASSSIZE; ++i) - password[i] = rand(); - - if (mhash_keygen_ext(KEYGEN_ALGORITHM, kg, - private_key, sizeof(private_key), - (unsigned char*)password, sizeof(password)) < 0) { - printf("Key generation failed\n"); - exit(1); - } -} - -void uw_sign(const char *in, char *out) { - MHASH td; - - td = mhash_hmac_init(HASH_ALGORITHM, private_key, sizeof(private_key), - mhash_get_hash_pblock(HASH_ALGORITHM)); - - mhash(td, in, strlen(in)); - if (mhash_hmac_deinit(td, out) < 0) - printf("Signing failed"); -} - static void *worker(void *data) { - int me = *(int *)data, retries_left = MAX_RETRIES; - uw_context ctx = new_context(); + int me = *(int *)data; + uw_context ctx = uw_request_new_context(); size_t buf_size = 2; char *buf = malloc(buf_size); - size_t path_copy_size = 0; - char *path_copy = malloc(path_copy_size); + uw_request_context rc = uw_new_request_context(); while (1) { - char *back = buf, *s, *post; - int sock, dont_close = 0; + char *back = buf; + int sock; pthread_mutex_lock(&queue_mutex); while (empty()) @@ -166,8 +74,8 @@ static void *worker(void *data) { printf("Handling connection with thread #%d.\n", me); while (1) { - unsigned retries_left = MAX_RETRIES; int r; + char *s1, *s2; if (back - buf == buf_size - 1) { char *new_buf; @@ -189,358 +97,40 @@ static void *worker(void *data) { break; } - //printf("Received %d bytes.\n", r); - back += r; *back = 0; - if (s = strstr(buf, "\r\n\r\n")) { - failure_kind fk; - int is_post = 0, do_normal_send = 1; - char *boundary = NULL; - size_t boundary_len; - char *cmd, *path, *headers, *inputs, *after_headers; - - //printf("All: %s\n", buf); - - s[2] = 0; - after_headers = s + 4; - - if (!(s = strstr(buf, "\r\n"))) { - fprintf(stderr, "No newline in buf\n"); - break; - } - - *s = 0; - headers = s + 2; - cmd = s = buf; - - //printf("Read: %s\n", buf); - - if (!strsep(&s, " ")) { - fprintf(stderr, "No first space in HTTP command\n"); - break; - } - - uw_set_headers(ctx, headers); - - if (!strcmp(cmd, "POST")) { - char *clen_s = uw_Basis_requestHeader(ctx, "Content-length"); - if (!clen_s) { - fprintf(stderr, "No Content-length with POST\n"); - goto done; - } - int clen = atoi(clen_s); - if (clen < 0) { - fprintf(stderr, "Negative Content-length with POST\n"); - goto done; - } - - while (back - after_headers < clen) { - if (back - buf == buf_size - 1) { - char *new_buf; - buf_size *= 2; - new_buf = realloc(buf, buf_size); - - back = new_buf + (back - buf); - headers = new_buf + (headers - buf); - uw_headers_moved(ctx, headers); - after_headers = new_buf + (after_headers - buf); - s = new_buf + (s - buf); - - buf = new_buf; - } - - r = recv(sock, back, buf_size - 1 - (back - buf), 0); - - if (r < 0) { - fprintf(stderr, "Recv failed\n"); - goto done; - } - - if (r == 0) { - printf("Connection closed.\n"); - goto done; - } - - back += r; - *back = 0; - } + if ((s1 = strstr(buf, "\r\n\r\n"))) { + request_result rr; - is_post = 1; + if ((s2 = strcasestr(buf, "\r\nContent-Length: ")) && s2 < s1) { + int clen; - clen_s = uw_Basis_requestHeader(ctx, "Content-type"); - if (clen_s && !strncasecmp(clen_s, "multipart/form-data", 19)) { - if (strncasecmp(clen_s + 19, "; boundary=", 11)) { - fprintf(stderr, "Bad multipart boundary spec"); - break; - } - - boundary = clen_s + 28; - boundary[0] = '-'; - boundary[1] = '-'; - boundary_len = strlen(boundary); - } - } else if (strcmp(cmd, "GET")) { - fprintf(stderr, "Not ready for non-GET/POST command: %s\n", cmd); - break; - } - - path = s; - if (!strsep(&s, " ")) { - fprintf(stderr, "No second space in HTTP command\n"); - break; - } - - if (!strcmp(path, "/.msgs")) { - char *id = uw_Basis_requestHeader(ctx, "UrWeb-Client"); - char *pass = uw_Basis_requestHeader(ctx, "UrWeb-Pass"); - - if (id && pass) { - unsigned idn = atoi(id); - uw_client_connect(idn, atoi(pass), sock); - dont_close = 1; - fprintf(stderr, "Processed request for messages by client %u\n\n", idn); - } - else { - fprintf(stderr, "Missing fields in .msgs request: %s, %s\n\n", id, pass); - } - break; - } - - if (boundary) { - char *part = after_headers, *after_sub_headers, *header, *after_header; - size_t part_len; - - part = strstr(part, boundary); - if (!part) { - fprintf(stderr, "Missing first multipart boundary\n"); + if (sscanf(s2 + 18, "%d\r\n", &clen) != 1) { + fprintf(stderr, "Malformed Content-Length header\n"); break; } - part += boundary_len; - - while (1) { - char *name = NULL, *filename = NULL, *type = NULL; - - if (part[0] == '-' && part[1] == '-') - break; - - if (*part != '\r') { - fprintf(stderr, "No \\r after multipart boundary\n"); - goto done; - } - ++part; - if (*part != '\n') { - fprintf(stderr, "No \\n after multipart boundary\n"); - goto done; - } - ++part; - - if (!(after_sub_headers = strstr(part, "\r\n\r\n"))) { - fprintf(stderr, "Missing end of headers after multipart boundary\n"); - goto done; - } - after_sub_headers[2] = 0; - after_sub_headers += 4; - - for (header = part; after_header = strstr(header, "\r\n"); header = after_header + 2) { - char *colon, *after_colon; - - *after_header = 0; - if (!(colon = strchr(header, ':'))) { - fprintf(stderr, "Missing colon in multipart sub-header\n"); - goto done; - } - *colon++ = 0; - if (*colon++ != ' ') { - fprintf(stderr, "No space after colon in multipart sub-header\n"); - goto done; - } - - if (!strcasecmp(header, "Content-Disposition")) { - if (strncmp(colon, "form-data; ", 11)) { - fprintf(stderr, "Multipart data is not \"form-data\"\n"); - goto done; - } - - for (colon += 11; after_colon = strchr(colon, '='); colon = after_colon) { - char *data; - after_colon[0] = 0; - if (after_colon[1] != '"') { - fprintf(stderr, "Disposition setting is missing initial quote\n"); - goto done; - } - data = after_colon+2; - if (!(after_colon = strchr(data, '"'))) { - fprintf(stderr, "Disposition setting is missing final quote\n"); - goto done; - } - after_colon[0] = 0; - ++after_colon; - if (after_colon[0] == ';' && after_colon[1] == ' ') - after_colon += 2; - - if (!strcasecmp(colon, "name")) - name = data; - else if (!strcasecmp(colon, "filename")) - filename = data; - } - } else if (!strcasecmp(header, "Content-Type")) { - type = colon; - } - } - - part = memmem(after_sub_headers, back - after_sub_headers, boundary, boundary_len); - if (!part) { - fprintf(stderr, "Missing boundary after multipart payload\n"); - goto done; - } - part[-2] = 0; - part_len = part - after_sub_headers - 2; - part[0] = 0; - part += boundary_len; - - if (filename) { - uw_Basis_file f = {filename, type, {part_len, after_sub_headers}}; - - if (uw_set_file_input(ctx, name, f)) { - puts(uw_error_message(ctx)); - goto done; - } - } else if (uw_set_input(ctx, name, after_sub_headers)) { - puts(uw_error_message(ctx)); - goto done; - } - } - } - else { - if (is_post) - inputs = after_headers; - else if (inputs = strchr(path, '?')) - *inputs++ = 0; - - if (inputs) { - char *name, *value; - - while (*inputs) { - name = inputs; - if (inputs = strchr(inputs, '&')) - *inputs++ = 0; - else - inputs = strchr(name, 0); - - if (value = strchr(name, '=')) { - *value++ = 0; - if (uw_set_input(ctx, name, value)) { - puts(uw_error_message(ctx)); - goto done; - } - } - else if (uw_set_input(ctx, name, "")) { - puts(uw_error_message(ctx)); - goto done; - } - } - } - } - - printf("Serving URI %s....\n", path); - - while (1) { - size_t path_len = strlen(path); - - uw_write_header(ctx, "HTTP/1.1 200 OK\r\n"); - - if (path_len + 1 > path_copy_size) { - path_copy_size = path_len + 1; - path_copy = realloc(path_copy, path_copy_size); - } - strcpy(path_copy, path); - fk = uw_begin(ctx, path_copy); - if (fk == SUCCESS || fk == RETURN_BLOB) { - uw_commit(ctx); - break; - } else if (fk == BOUNDED_RETRY) { - if (retries_left) { - printf("Error triggers bounded retry: %s\n", uw_error_message(ctx)); - --retries_left; - } - else { - printf("Fatal error (out of retries): %s\n", uw_error_message(ctx)); - - try_rollback(ctx); - - uw_reset_keep_error_message(ctx); - uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); - uw_write_header(ctx, "Content-type: text/plain\r\n"); - uw_write(ctx, "Fatal error (out of retries): "); - uw_write(ctx, uw_error_message(ctx)); - uw_write(ctx, "\n"); - - break; - } - } else if (fk == UNLIMITED_RETRY) - printf("Error triggers unlimited retry: %s\n", uw_error_message(ctx)); - else if (fk == FATAL) { - printf("Fatal error: %s\n", uw_error_message(ctx)); - - try_rollback(ctx); - - uw_reset_keep_error_message(ctx); - uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\r\n"); - uw_write_header(ctx, "Content-type: text/html\r\n"); - uw_write(ctx, "Fatal Error"); - uw_write(ctx, "Fatal error: "); - uw_write(ctx, uw_error_message(ctx)); - uw_write(ctx, "\n"); - - break; - } else { - printf("Unknown uw_handle return code!\n"); - - try_rollback(ctx); - uw_reset_keep_request(ctx); - uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); - uw_write_header(ctx, "Content-type: text/plain\r\n"); - uw_write(ctx, "Unknown uw_handle return code!\n"); - - break; - } - - if (try_rollback(ctx)) - break; - - uw_reset_keep_request(ctx); + if (s1 + 4 + clen > back) + continue; } + rr = uw_request(rc, ctx, buf, back - buf, sock); uw_send(ctx, sock); - printf("Done with client.\n\n"); - uw_memstats(ctx); + if (rr == SERVED || rr == FAILED) + close(sock); + else if (rr != KEEP_OPEN) + fprintf(stderr, "Illegal uw_request return code: %d\n", rr); + break; } } - done: - if (!dont_close) - close(sock); uw_reset(ctx); } } -static void *client_pruner(void *data) { - uw_context ctx = new_context(); - - if (!ctx) - exit(1); - - while (1) { - uw_prune_clients(ctx); - sleep(5); - } -} - static void help(char *cmd) { printf("Usage: %s [-p ] [-t ]\n", cmd); } @@ -550,32 +140,6 @@ static void sigint(int signum) { exit(0); } -static void initialize() { - uw_context ctx; - failure_kind fk; - - init_crypto(); - - ctx = new_context(); - - if (!ctx) - exit(1); - - for (fk = uw_initialize(ctx); fk == UNLIMITED_RETRY; fk = uw_initialize(ctx)) { - printf("Unlimited retry during init: %s\n", uw_error_message(ctx)); - uw_db_rollback(ctx); - uw_reset(ctx); - } - - if (fk != SUCCESS) { - printf("Failed to initialize database! %s\n", uw_error_message(ctx)); - uw_db_rollback(ctx); - exit(1); - } - - uw_free(ctx); -} - int main(int argc, char *argv[]) { // The skeleton for this function comes from Beej's sockets tutorial. int sockfd; // listen on sock_fd @@ -622,8 +186,7 @@ int main(int argc, char *argv[]) { } } - uw_global_init(); - initialize(); + uw_request_init(); names = calloc(nthreads, sizeof(int)); diff --git a/src/c/request.c b/src/c/request.c new file mode 100644 index 00000000..b13c6118 --- /dev/null +++ b/src/c/request.c @@ -0,0 +1,467 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "urweb.h" + +#define MAX_RETRIES 5 + +static int try_rollback(uw_context ctx) { + int r = uw_rollback(ctx); + + if (r) { + printf("Error running SQL ROLLBACK\n"); + uw_reset(ctx); + uw_write(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); + uw_write(ctx, "Content-type: text/plain\r\n\r\n"); + uw_write(ctx, "Error running SQL ROLLBACK\n"); + } + + return r; +} + +uw_context uw_request_new_context() { + uw_context ctx = uw_init(); + int retries_left = MAX_RETRIES; + + while (1) { + failure_kind fk = uw_begin_init(ctx); + + if (fk == SUCCESS) { + printf("Database connection initialized.\n"); + break; + } else if (fk == BOUNDED_RETRY) { + if (retries_left) { + printf("Initialization error triggers bounded retry: %s\n", uw_error_message(ctx)); + --retries_left; + } else { + printf("Fatal initialization error (out of retries): %s\n", uw_error_message(ctx)); + uw_free(ctx); + return NULL; + } + } else if (fk == UNLIMITED_RETRY) + printf("Initialization error triggers unlimited retry: %s\n", uw_error_message(ctx)); + else if (fk == FATAL) { + printf("Fatal initialization error: %s\n", uw_error_message(ctx)); + uw_free(ctx); + return NULL; + } else { + printf("Unknown uw_begin_init return code!\n"); + uw_free(ctx); + return NULL; + } + } + + return ctx; +} + +#define KEYSIZE 16 +#define PASSSIZE 4 + +#define HASH_ALGORITHM MHASH_SHA256 +#define HASH_BLOCKSIZE 32 +#define KEYGEN_ALGORITHM KEYGEN_MCRYPT + +int uw_hash_blocksize = HASH_BLOCKSIZE; + +static int password[PASSSIZE]; +static unsigned char private_key[KEYSIZE]; + +static void init_crypto() { + KEYGEN kg = {{HASH_ALGORITHM, HASH_ALGORITHM}}; + int i; + + assert(mhash_get_block_size(HASH_ALGORITHM) == HASH_BLOCKSIZE); + + for (i = 0; i < PASSSIZE; ++i) + password[i] = rand(); + + if (mhash_keygen_ext(KEYGEN_ALGORITHM, kg, + private_key, sizeof(private_key), + (unsigned char*)password, sizeof(password)) < 0) { + printf("Key generation failed\n"); + exit(1); + } +} + +void uw_request_init() { + uw_context ctx; + failure_kind fk; + + uw_global_init(); + + ctx = uw_request_new_context(); + + if (!ctx) + exit(1); + + for (fk = uw_initialize(ctx); fk == UNLIMITED_RETRY; fk = uw_initialize(ctx)) { + printf("Unlimited retry during init: %s\n", uw_error_message(ctx)); + uw_db_rollback(ctx); + uw_reset(ctx); + } + + if (fk != SUCCESS) { + printf("Failed to initialize database! %s\n", uw_error_message(ctx)); + uw_db_rollback(ctx); + exit(1); + } + + uw_free(ctx); + + init_crypto(); +} + +void uw_sign(const char *in, char *out) { + MHASH td; + + td = mhash_hmac_init(HASH_ALGORITHM, private_key, sizeof(private_key), + mhash_get_hash_pblock(HASH_ALGORITHM)); + + mhash(td, in, strlen(in)); + if (mhash_hmac_deinit(td, out) < 0) + printf("Signing failed"); +} + +typedef struct uw_rc { + size_t path_copy_size; + char *path_copy; +} *uw_request_context; + +uw_request_context uw_new_request_context(void) { + uw_request_context r = malloc(sizeof(struct uw_rc)); + r->path_copy_size = 0; + r->path_copy = malloc(0); + return r; +} + +void uw_free_request_context(uw_request_context r) { + free(r->path_copy); + free(r); +} + +request_result uw_request(uw_request_context rc, uw_context ctx, char *request, size_t request_len, int sock) { + int retries_left = MAX_RETRIES; + char *s; + failure_kind fk; + int is_post = 0, do_normal_send = 1; + char *boundary = NULL; + size_t boundary_len; + char *cmd, *path, *headers, *inputs, *after_headers; + + if (!(s = strstr(request, "\r\n\r\n"))) { + fprintf(stderr, "No end of headers found in request\n"); + return FAILED; + } + + s[2] = 0; + after_headers = s + 4; + + if (!(s = strstr(request, "\r\n"))) { + fprintf(stderr, "No newline in request\n"); + return FAILED; + } + + *s = 0; + headers = s + 2; + cmd = s = request; + + if (!strsep(&s, " ")) { + fprintf(stderr, "No first space in HTTP command\n"); + return FAILED; + } + + uw_set_headers(ctx, headers); + + if (!strcmp(cmd, "POST")) { + char *clen_s = uw_Basis_requestHeader(ctx, "Content-length"); + if (!clen_s) { + fprintf(stderr, "No Content-length with POST\n"); + return FAILED; + } + int clen = atoi(clen_s); + if (clen < 0) { + fprintf(stderr, "Negative Content-length with POST\n"); + return FAILED; + } + + if (request + request_len - after_headers < clen) { + fprintf(stderr, "Request doesn't contain all POST data (according to Content-Length)\n"); + return FAILED; + } + + is_post = 1; + + clen_s = uw_Basis_requestHeader(ctx, "Content-type"); + if (clen_s && !strncasecmp(clen_s, "multipart/form-data", 19)) { + if (strncasecmp(clen_s + 19, "; boundary=", 11)) { + fprintf(stderr, "Bad multipart boundary spec"); + return FAILED; + } + + boundary = clen_s + 28; + boundary[0] = '-'; + boundary[1] = '-'; + boundary_len = strlen(boundary); + } + } else if (strcmp(cmd, "GET")) { + fprintf(stderr, "Not ready for non-GET/POST command: %s\n", cmd); + return FAILED; + } + + path = s; + if (!strsep(&s, " ")) { + fprintf(stderr, "No second space in HTTP command\n"); + return FAILED; + } + + if (!strcmp(path, "/.msgs")) { + char *id = uw_Basis_requestHeader(ctx, "UrWeb-Client"); + char *pass = uw_Basis_requestHeader(ctx, "UrWeb-Pass"); + + if (sock < 0) { + fprintf(stderr, ".msgs requested, but not socket supplied\n"); + return FAILED; + } + + if (id && pass) { + unsigned idn = atoi(id); + uw_client_connect(idn, atoi(pass), sock); + fprintf(stderr, "Processed request for messages by client %u\n\n", idn); + return KEEP_OPEN; + } + else { + fprintf(stderr, "Missing fields in .msgs request: %s, %s\n\n", id, pass); + return FAILED; + } + } + + if (boundary) { + char *part = after_headers, *after_sub_headers, *header, *after_header; + size_t part_len; + + part = strstr(part, boundary); + if (!part) { + fprintf(stderr, "Missing first multipart boundary\n"); + return FAILED; + } + part += boundary_len; + + while (1) { + char *name = NULL, *filename = NULL, *type = NULL; + + if (part[0] == '-' && part[1] == '-') + break; + + if (*part != '\r') { + fprintf(stderr, "No \\r after multipart boundary\n"); + return FAILED; + } + ++part; + if (*part != '\n') { + fprintf(stderr, "No \\n after multipart boundary\n"); + return FAILED; + } + ++part; + + if (!(after_sub_headers = strstr(part, "\r\n\r\n"))) { + fprintf(stderr, "Missing end of headers after multipart boundary\n"); + return FAILED; + } + after_sub_headers[2] = 0; + after_sub_headers += 4; + + for (header = part; after_header = strstr(header, "\r\n"); header = after_header + 2) { + char *colon, *after_colon; + + *after_header = 0; + if (!(colon = strchr(header, ':'))) { + fprintf(stderr, "Missing colon in multipart sub-header\n"); + return FAILED; + } + *colon++ = 0; + if (*colon++ != ' ') { + fprintf(stderr, "No space after colon in multipart sub-header\n"); + return FAILED; + } + + if (!strcasecmp(header, "Content-Disposition")) { + if (strncmp(colon, "form-data; ", 11)) { + fprintf(stderr, "Multipart data is not \"form-data\"\n"); + return FAILED; + } + + for (colon += 11; after_colon = strchr(colon, '='); colon = after_colon) { + char *data; + after_colon[0] = 0; + if (after_colon[1] != '"') { + fprintf(stderr, "Disposition setting is missing initial quote\n"); + return FAILED; + } + data = after_colon+2; + if (!(after_colon = strchr(data, '"'))) { + fprintf(stderr, "Disposition setting is missing final quote\n"); + return FAILED; + } + after_colon[0] = 0; + ++after_colon; + if (after_colon[0] == ';' && after_colon[1] == ' ') + after_colon += 2; + + if (!strcasecmp(colon, "name")) + name = data; + else if (!strcasecmp(colon, "filename")) + filename = data; + } + } else if (!strcasecmp(header, "Content-Type")) { + type = colon; + } + } + + part = memmem(after_sub_headers, request + request_len - after_sub_headers, boundary, boundary_len); + if (!part) { + fprintf(stderr, "Missing boundary after multipart payload\n"); + return FAILED; + } + part[-2] = 0; + part_len = part - after_sub_headers - 2; + part[0] = 0; + part += boundary_len; + + if (filename) { + uw_Basis_file f = {filename, type, {part_len, after_sub_headers}}; + + if (uw_set_file_input(ctx, name, f)) { + fprintf(stderr, "%s\n", uw_error_message(ctx)); + return FAILED; + } + } else if (uw_set_input(ctx, name, after_sub_headers)) { + fprintf(stderr, "%s\n", uw_error_message(ctx)); + return FAILED; + } + } + } + else { + if (is_post) + inputs = after_headers; + else if (inputs = strchr(path, '?')) + *inputs++ = 0; + + if (inputs) { + char *name, *value; + + while (*inputs) { + name = inputs; + if (inputs = strchr(inputs, '&')) + *inputs++ = 0; + else + inputs = strchr(name, 0); + + if (value = strchr(name, '=')) { + *value++ = 0; + if (uw_set_input(ctx, name, value)) { + fprintf(stderr, "%s\n", uw_error_message(ctx)); + return FAILED; + } + } + else if (uw_set_input(ctx, name, "")) { + fprintf(stderr, "%s\n", uw_error_message(ctx)); + return FAILED; + } + } + } + } + + printf("Serving URI %s....\n", path); + + while (1) { + size_t path_len = strlen(path); + + uw_write_header(ctx, "HTTP/1.1 200 OK\r\n"); + + if (path_len + 1 > rc->path_copy_size) { + rc->path_copy_size = path_len + 1; + rc->path_copy = realloc(rc->path_copy, rc->path_copy_size); + } + strcpy(rc->path_copy, path); + fk = uw_begin(ctx, rc->path_copy); + if (fk == SUCCESS || fk == RETURN_BLOB) { + uw_commit(ctx); + return SERVED; + } else if (fk == BOUNDED_RETRY) { + if (retries_left) { + printf("Error triggers bounded retry: %s\n", uw_error_message(ctx)); + --retries_left; + } + else { + printf("Fatal error (out of retries): %s\n", uw_error_message(ctx)); + + try_rollback(ctx); + + uw_reset_keep_error_message(ctx); + uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); + uw_write_header(ctx, "Content-type: text/plain\r\n"); + uw_write(ctx, "Fatal error (out of retries): "); + uw_write(ctx, uw_error_message(ctx)); + uw_write(ctx, "\n"); + + return FAILED; + } + } else if (fk == UNLIMITED_RETRY) + printf("Error triggers unlimited retry: %s\n", uw_error_message(ctx)); + else if (fk == FATAL) { + printf("Fatal error: %s\n", uw_error_message(ctx)); + + try_rollback(ctx); + + uw_reset_keep_error_message(ctx); + uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\r\n"); + uw_write_header(ctx, "Content-type: text/html\r\n"); + uw_write(ctx, "Fatal Error"); + uw_write(ctx, "Fatal error: "); + uw_write(ctx, uw_error_message(ctx)); + uw_write(ctx, "\n"); + + return FAILED; + } else { + printf("Unknown uw_handle return code!\n"); + + try_rollback(ctx); + + uw_reset_keep_request(ctx); + uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); + uw_write_header(ctx, "Content-type: text/plain\r\n"); + uw_write(ctx, "Unknown uw_handle return code!\n"); + + return FAILED; + } + + if (try_rollback(ctx)) + return FAILED; + + uw_reset_keep_request(ctx); + } +} + +void *client_pruner(void *data) { + uw_context ctx = uw_request_new_context(); + + if (!ctx) + exit(1); + + while (1) { + uw_prune_clients(ctx); + sleep(5); + } +} diff --git a/src/compiler.sml b/src/compiler.sml index 4209426f..c7c2f65e 100644 --- a/src/compiler.sml +++ b/src/compiler.sml @@ -884,11 +884,13 @@ val toSqlify = transform sqlify "sqlify" o toMono_opt2 fun compileC {cname, oname, ename, libs, profile, debug, link = link'} = let val urweb_o = clibFile "urweb.o" + val request_o = clibFile "request.o" val driver_o = clibFile "driver.o" val compile = "gcc " ^ Config.gccArgs ^ " -Wstrict-prototypes -Werror -O3 -I " ^ Config.includ ^ " -c " ^ cname ^ " -o " ^ oname - val link = "gcc -Werror -O3 -lm -lmhash -pthread " ^ libs ^ " " ^ urweb_o ^ " " ^ oname ^ " " ^ driver_o ^ " -o " ^ ename + val link = "gcc -Werror -O3 -lm -lmhash -pthread " ^ libs ^ " " ^ urweb_o ^ " " ^ oname + ^ " " ^ request_o ^ " " ^ driver_o ^ " -o " ^ ename val (compile, link) = if profile then -- cgit v1.2.3 From 20b1f5880b6553c42f2a71fd5ad38b865faed6b6 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 12 Jul 2009 13:16:05 -0400 Subject: MySQL query gets up to C linking --- doc/manual.tex | 6 +- src/cjr_print.sml | 7 +- src/mysql.sml | 513 +++++++++++++++++++++++++++++++++++++++++++++++++++++- src/postgres.sml | 31 +++- src/settings.sig | 7 +- src/settings.sml | 10 +- 6 files changed, 547 insertions(+), 27 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 22c219ad..3b57dc1b 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -137,6 +137,7 @@ Here is the complete list of directive forms. ``FFI'' stands for ``foreign func \item \texttt{effectful Module.ident} registers an FFI function or transaction as having side effects. The optimizer avoids removing, moving, or duplicating calls to such functions. Every effectful FFI function must be registered, or the optimizer may make invalid transformations. \item \texttt{exe FILENAME} sets the filename to which to write the output executable. The default for file \texttt{P.urp} is \texttt{P.exe}. \item \texttt{ffi FILENAME} reads the file \texttt{FILENAME.urs} to determine the interface to a new FFI module. The name of the module is calculated from \texttt{FILENAME} in the same way as for normal source files. See the files \texttt{include/urweb.h} and \texttt{src/c/urweb.c} for examples of C headers and implementations for FFI modules. In general, every type or value \texttt{Module.ident} becomes \texttt{uw\_Module\_ident} in C. +\item \texttt{header FILENAME} adds \texttt{FILENAME} to the list of files to be \texttt{\#include}d in C sources. This is most useful for interfacing with new FFI modules. \item \texttt{jsFunc Module.ident=name} gives the JavaScript name of an FFI value. \item \texttt{library FILENAME} parses \texttt{FILENAME.urp} and merges its contents with the rest of the current file's contents. \item \texttt{link FILENAME} adds \texttt{FILENAME} to the list of files to be passed to the GCC linker at the end of compilation. This is most useful for importing extra libraries needed by new FFI modules. @@ -193,7 +194,7 @@ We give the Ur language definition in \LaTeX $\;$ math mode, since that is prett We often write syntax like $e^*$ to indicate zero or more copies of $e$, $e^+$ to indicate one or more copies, and $e,^*$ and $e,^+$ to indicate multiple copies separated by commas. Another separator may be used in place of a comma. The $e$ term may be surrounded by parentheses to indicate grouping; those parentheses should not be included in the actual ASCII. -We write $\ell$ for literals of the primitive types, for the most part following C conventions. There are $\mt{int}$, $\mt{float}$, and $\mt{string}$ literals. +We write $\ell$ for literals of the primitive types, for the most part following C conventions. There are $\mt{int}$, $\mt{float}$, $\mt{char}$, and $\mt{string}$ literals. Character literals follow the SML convention instead of the C convention, written like \texttt{\#"a"} instead of \texttt{'a'}. This version of the manual doesn't include operator precedences; see \texttt{src/urweb.grm} for that. @@ -610,7 +611,7 @@ $$\infer{\Gamma \vdash \mt{map} \; f \; (c_1 \rc c_2) \equiv \mt{map} \; f \; c_ \subsection{Expression Typing} -We assume the existence of a function $T$ assigning types to literal constants. It maps integer constants to $\mt{Basis}.\mt{int}$, float constants to $\mt{Basis}.\mt{float}$, and string constants to $\mt{Basis}.\mt{string}$. +We assume the existence of a function $T$ assigning types to literal constants. It maps integer constants to $\mt{Basis}.\mt{int}$, float constants to $\mt{Basis}.\mt{float}$, character constants to $\mt{Basis}.\mt{char}$, and string constants to $\mt{Basis}.\mt{string}$. We also refer to a function $\mathcal I$, such that $\mathcal I(\tau)$ ``uses an oracle'' to instantiate all constructor function arguments at the beginning of $\tau$ that are marked implicit; i.e., replace $x_1 ::: \kappa_1 \to \ldots \to x_n ::: \kappa_n \to \tau$ with $[x_1 \mapsto c_1]\ldots[x_n \mapsto c_n]\tau$, where the $c_i$s are inferred and $\tau$ does not start like $x ::: \kappa \to \tau'$. @@ -1147,6 +1148,7 @@ The idea behind Ur is to serve as the ideal host for embedded domain-specific la $$\begin{array}{l} \mt{type} \; \mt{int} \\ \mt{type} \; \mt{float} \\ + \mt{type} \; \mt{char} \\ \mt{type} \; \mt{string} \\ \mt{type} \; \mt{time} \\ \mt{type} \; \mt{blob} \\ diff --git a/src/cjr_print.sml b/src/cjr_print.sml index 7d1120b4..fcfa402e 100644 --- a/src/cjr_print.sml +++ b/src/cjr_print.sml @@ -1652,7 +1652,7 @@ fun p_exp' par env (e, loc) = #query (Settings.currentDbms ()) {loc = loc, - numCols = length outputs, + cols = map (fn (_, t) => sql_type_in env t) outputs, doCols = doCols}] | SOME (id, query) => box [p_list_sepi newline @@ -1675,7 +1675,7 @@ fun p_exp' par env (e, loc) = id = id, query = query, inputs = map #2 inputs, - numCols = length outputs, + cols = map (fn (_, t) => sql_type_in env t) outputs, doCols = doCols}], newline, @@ -2797,7 +2797,8 @@ fun p_sql env (ds, _) = box [string "uw_", string (CharVector.map Char.toLower x), space, - p_sqltype env (t, ErrorMsg.dummySpan)]) xts, + string (#p_sql_type (Settings.currentDbms ()) + (sql_type_in env t))]) xts, case (pk, csts) of ("", []) => box [] | _ => string ",", diff --git a/src/mysql.sml b/src/mysql.sml index 7b02c787..2fcdef2d 100644 --- a/src/mysql.sml +++ b/src/mysql.sml @@ -31,6 +31,30 @@ open Settings open Print.PD open Print +fun p_sql_type t = + case t of + Int => "bigint" + | Float => "double" + | String => "longtext" + | Bool => "bool" + | Time => "timestamp" + | Blob => "longblob" + | Channel => "bigint" + | Client => "int" + | Nullable t => p_sql_type t + +fun p_buffer_type t = + case t of + Int => "MYSQL_TYPE_LONGLONG" + | Float => "MYSQL_TYPE_DOUBLE" + | String => "MYSQL_TYPE_STRING" + | Bool => "MYSQL_TYPE_LONG" + | Time => "MYSQL_TYPE_TIME" + | Blob => "MYSQL_TYPE_BLOB" + | Channel => "MYSQL_TYPE_LONGLONG" + | Client => "MYSQL_TYPE_LONG" + | Nullable t => p_buffer_type t + fun init {dbstring, prepared = ss, tables, views, sequences} = let val host = ref NONE @@ -138,6 +162,10 @@ fun init {dbstring, prepared = ss, tables, views, sequences} = newline, uhoh true "Error preparing statement: %s" ["msg"]], string "}", + newline, + string "conn->p", + string (Int.toString i), + string " = stmt;", newline] end) ss, @@ -253,12 +281,484 @@ fun init {dbstring, prepared = ss, tables, views, sequences} = newline] end -fun query _ = raise Fail "MySQL query" -fun queryPrepared _ = raise Fail "MySQL queryPrepared" -fun dml _ = raise Fail "MySQL dml" -fun dmlPrepared _ = raise Fail "MySQL dmlPrepared" -fun nextval _ = raise Fail "MySQL nextval" -fun nextvalPrepared _ = raise Fail "MySQL nextvalPrepared" +fun p_getcol {wontLeakStrings = _, col = i, typ = t} = + let + fun getter t = + case t of + String => box [string "({", + newline, + string "uw_Basis_string s = uw_malloc(ctx, length", + string (Int.toString i), + string " + 1);", + newline, + string "out[", + string (Int.toString i), + string "].buffer = s;", + newline, + string "out[", + string (Int.toString i), + string "].buffer_length = length", + string (Int.toString i), + string " + 1;", + newline, + string "mysql_stmt_fetch_column(stmt, &out[", + string (Int.toString i), + string "], ", + string (Int.toString i), + string ", 0);", + newline, + string "s[length", + string (Int.toString i), + string "] = 0;", + newline, + string "s;", + newline, + string "})"] + | Blob => box [string "({", + newline, + string "uw_Basis_blob b = {length", + string (Int.toString i), + string ", uw_malloc(ctx, length", + string (Int.toString i), + string ")};", + newline, + string "out[", + string (Int.toString i), + string "].buffer = b.data;", + newline, + string "out[", + string (Int.toString i), + string "].buffer_length = length", + string (Int.toString i), + string ";", + newline, + string "mysql_stmt_fetch_column(stmt, &out[", + string (Int.toString i), + string "], ", + string (Int.toString i), + string ", 0);", + newline, + string "b;", + newline, + string "})"] + | Time => box [string "({", + string "MYSQL_TIME *mt = buffer", + string (Int.toString i), + string ";", + newline, + newline, + string "struct tm t = {mt->second, mt->minute, mt->hour, mt->day, mt->month, mt->year, 0, 0, -1};", + newline, + string "mktime(&tm);", + newline, + string "})"] + | _ => box [string "buffer", + string (Int.toString i)] + in + case t of + Nullable t => box [string "(is_null", + string (Int.toString i), + string " ? NULL : ", + case t of + String => getter t + | _ => box [string "({", + newline, + string (p_sql_ctype t), + space, + string "*tmp = uw_malloc(ctx, sizeof(", + string (p_sql_ctype t), + string "));", + newline, + string "*tmp = ", + getter t, + string ";", + newline, + string "tmp;", + newline, + string "})"], + string ")"] + | _ => box [string "(is_null", + string (Int.toString i), + string " ? ", + box [string "({", + string (p_sql_ctype t), + space, + string "tmp;", + newline, + string "uw_error(ctx, FATAL, \"Unexpectedly NULL field #", + string (Int.toString i), + string "\");", + newline, + string "tmp;", + newline, + string "})"], + string " : ", + getter t, + string ")"] + end + +fun queryCommon {loc, query, cols, doCols} = + box [string "int n, r;", + newline, + string "MYSQL_BIND out[", + string (Int.toString (length cols)), + string "];", + newline, + p_list_sepi (box []) (fn i => fn t => + let + fun buffers t = + case t of + String => box [string "unsigned long length", + string (Int.toString i), + string ";", + newline] + | Blob => box [string "unsigned long length", + string (Int.toString i), + string ";", + newline] + | _ => box [string (p_sql_ctype t), + space, + string "buffer", + string (Int.toString i), + string ";", + newline] + in + box [string "my_bool is_null", + string (Int.toString i), + string ";", + newline, + case t of + Nullable t => buffers t + | _ => buffers t, + newline] + end) cols, + newline, + + string "memset(out, 0, sizeof out);", + newline, + p_list_sepi (box []) (fn i => fn t => + let + fun buffers t = + case t of + String => box [] + | Blob => box [] + | _ => box [string "out[", + string (Int.toString i), + string "].buffer = &buffer", + string (Int.toString i), + string ";", + newline] + in + box [string "out[", + string (Int.toString i), + string "].buffer_type = ", + string (p_buffer_type t), + string ";", + newline, + string "out[", + string (Int.toString i), + string "].is_null = &is_null", + string (Int.toString i), + string ";", + newline, + + case t of + Nullable t => buffers t + | _ => buffers t, + newline] + end) cols, + newline, + + string "if (mysql_stmt_execute(stmt)) uw_error(ctx, FATAL, \"", + string (ErrorMsg.spanToString loc), + string ": Error executing query\");", + newline, + newline, + + string "if (mysql_stmt_store_result(stmt)) uw_error(ctx, FATAL, \"", + string (ErrorMsg.spanToString loc), + string ": Error storing query result\");", + newline, + newline, + + string "if (mysql_stmt_bind_result(stmt, out)) uw_error(ctx, FATAL, \"", + string (ErrorMsg.spanToString loc), + string ": Error binding query result\");", + newline, + newline, + + string "uw_end_region(ctx);", + newline, + string "while ((r = mysql_stmt_fetch(stmt)) == 0) {", + newline, + doCols p_getcol, + string "}", + newline, + newline, + + string "if (r != MYSQL_NO_DATA) uw_error(ctx, FATAL, \"", + string (ErrorMsg.spanToString loc), + string ": query result fetching failed\");", + newline] + +fun query {loc, cols, doCols} = + box [string "uw_conn *conn = uw_get_db(ctx);", + newline, + string "MYSQL_stmt *stmt = mysql_stmt_init(conn->conn);", + newline, + string "if (stmt == NULL) uw_error(ctx, \"", + string (ErrorMsg.spanToString loc), + string ": can't allocate temporary prepared statement\");", + newline, + string "uw_push_cleanup(ctx, (void (*)(void *))mysql_stmt_close, stmt);", + newline, + string "if (mysql_stmt_prepare(stmt, query, strlen(query))) uw_error(ctx, FATAL, \"", + string (ErrorMsg.spanToString loc), + string "\");", + newline, + newline, + + p_list_sepi (box []) (fn i => fn t => + let + fun buffers t = + case t of + String => box [] + | Blob => box [] + | _ => box [string "out[", + string (Int.toString i), + string "].buffer = &buffer", + string (Int.toString i), + string ";", + newline] + in + box [string "in[", + string (Int.toString i), + string "].buffer_type = ", + string (p_buffer_type t), + string ";", + newline, + + case t of + Nullable t => box [string "in[", + string (Int.toString i), + string "].is_null = &is_null", + string (Int.toString i), + string ";", + newline, + buffers t] + | _ => buffers t, + newline] + end) cols, + newline, + + queryCommon {loc = loc, cols = cols, doCols = doCols, query = string "query"}, + + string "uw_pop_cleanup(ctx);", + newline] + +fun p_ensql t e = + case t of + Int => box [string "uw_Basis_attrifyInt(ctx, ", e, string ")"] + | Float => box [string "uw_Basis_attrifyFloat(ctx, ", e, string ")"] + | String => e + | Bool => box [string "(", e, string " ? \"TRUE\" : \"FALSE\")"] + | Time => box [string "uw_Basis_attrifyTime(ctx, ", e, string ")"] + | Blob => box [e, string ".data"] + | Channel => box [string "uw_Basis_attrifyChannel(ctx, ", e, string ")"] + | Client => box [string "uw_Basis_attrifyClient(ctx, ", e, string ")"] + | Nullable String => e + | Nullable t => box [string "(", + e, + string " == NULL ? NULL : ", + p_ensql t (box [string "(*", e, string ")"]), + string ")"] + +fun queryPrepared {loc, id, query, inputs, cols, doCols} = + box [string "uw_conn *conn = uw_get_db(ctx);", + newline, + string "MYSQL_BIND in[", + string (Int.toString (length inputs)), + string "];", + newline, + p_list_sepi (box []) (fn i => fn t => + let + fun buffers t = + case t of + String => box [string "unsigned long in_length", + string (Int.toString i), + string ";", + newline] + | Blob => box [string "unsigned long in_length", + string (Int.toString i), + string ";", + newline] + | Time => box [string (p_sql_ctype t), + space, + string "in_buffer", + string (Int.toString i), + string ";", + newline] + | _ => box [] + in + box [case t of + Nullable t => box [string "my_bool in_is_null", + string (Int.toString i), + string ";", + newline, + buffers t] + | _ => buffers t, + newline] + end) inputs, + string "MYSQL_STMT *stmt = conn->p", + string (Int.toString id), + string ";", + newline, + newline, + + string "memset(in, 0, sizeof in);", + newline, + p_list_sepi (box []) (fn i => fn t => + let + fun buffers t = + case t of + String => box [string "in[", + string (Int.toString i), + string "].buffer = arg", + string (Int.toString (i + 1)), + string ";", + newline, + string "in_length", + string (Int.toString i), + string "= in[", + string (Int.toString i), + string "].buffer_length = strlen(arg", + string (Int.toString (i + 1)), + string ");", + newline, + string "in[", + string (Int.toString i), + string "].length = &in_length", + string (Int.toString i), + string ";", + newline] + | Blob => box [string "in[", + string (Int.toString i), + string "].buffer = arg", + string (Int.toString (i + 1)), + string ".data;", + newline, + string "in_length", + string (Int.toString i), + string "= in[", + string (Int.toString i), + string "].buffer_length = arg", + string (Int.toString (i + 1)), + string ".size;", + newline, + string "in[", + string (Int.toString i), + string "].length = &in_length", + string (Int.toString i), + string ";", + newline] + | Time => + let + fun oneField dst src = + box [string "in_buffer", + string (Int.toString i), + string ".", + string dst, + string " = tms.tm_", + string src, + string ";", + newline] + in + box [string "({", + newline, + string "struct tm tms;", + newline, + string "if (localtime_r(&arg", + string (Int.toString (i + 1)), + string ", &tm) == NULL) uw_error(\"", + string (ErrorMsg.spanToString loc), + string ": error converting to MySQL time\");", + newline, + oneField "year" "year", + oneField "month" "mon", + oneField "day" "mday", + oneField "hour" "hour", + oneField "minute" "min", + oneField "second" "sec", + newline, + string "in[", + string (Int.toString i), + string "].buffer = &in_buffer", + string (Int.toString i), + string ";", + newline] + end + + | _ => box [string "in[", + string (Int.toString i), + string "].buffer = &arg", + string (Int.toString (i + 1)), + string ";", + newline] + in + box [string "in[", + string (Int.toString i), + string "].buffer_type = ", + string (p_buffer_type t), + string ";", + newline, + + case t of + Nullable t => box [string "in[", + string (Int.toString i), + string "].is_null = &in_is_null", + string (Int.toString i), + string ";", + newline, + string "if (arg", + string (Int.toString (i + 1)), + string " == NULL) {", + newline, + box [string "in_is_null", + string (Int.toString i), + string " = 1;", + newline], + string "} else {", + box [case t of + String => box [] + | _ => + box [string (p_sql_ctype t), + space, + string "arg", + string (Int.toString (i + 1)), + string " = *arg", + string (Int.toString (i + 1)), + string ";", + newline], + string "in_is_null", + string (Int.toString i), + string " = 0;", + newline, + buffers t, + newline]] + + | _ => buffers t, + newline] + end) inputs, + newline, + + queryCommon {loc = loc, cols = cols, doCols = doCols, query = box [string "\"", + string (String.toString query), + string "\""]}] + +fun dml _ = box [] +fun dmlPrepared _ = box [] +fun nextval _ = box [] +fun nextvalPrepared _ = box [] val () = addDbms {name = "mysql", header = "mysql/mysql.h", @@ -276,6 +776,7 @@ val () = addDbms {name = "mysql", string "}", newline], init = init, + p_sql_type = p_sql_type, query = query, queryPrepared = queryPrepared, dml = dml, diff --git a/src/postgres.sml b/src/postgres.sml index 07a68607..ca71798f 100644 --- a/src/postgres.sml +++ b/src/postgres.sml @@ -34,6 +34,18 @@ open Print val ident = String.translate (fn #"'" => "PRIME" | ch => str ch) +fun p_sql_type t = + case t of + Int => "int8" + | Float => "float8" + | String => "text" + | Bool => "bool" + | Time => "timestamp" + | Blob => "bytea" + | Channel => "int8" + | Client => "int4" + | Nullable t => p_sql_type t + fun p_sql_type_base t = case t of Int => "bigint" @@ -540,7 +552,7 @@ fun p_getcol {wontLeakStrings, col = i, typ = t} = getter t end -fun queryCommon {loc, query, numCols, doCols} = +fun queryCommon {loc, query, cols, doCols} = box [string "int n, i;", newline, newline, @@ -564,7 +576,7 @@ fun queryCommon {loc, query, numCols, doCols} = newline, string "if (PQnfields(res) != ", - string (Int.toString numCols), + string (Int.toString (length cols)), string ") {", newline, box [string "int nf = PQnfields(res);", @@ -574,7 +586,7 @@ fun queryCommon {loc, query, numCols, doCols} = string "uw_error(ctx, FATAL, \"", string (ErrorMsg.spanToString loc), string ": Query returned %d columns instead of ", - string (Int.toString numCols), + string (Int.toString (length cols)), string ":\\n%s\\n%s\", nf, ", query, string ", PQerrorMessage(conn));", @@ -598,13 +610,13 @@ fun queryCommon {loc, query, numCols, doCols} = string "uw_pop_cleanup(ctx);", newline] -fun query {loc, numCols, doCols} = +fun query {loc, cols, doCols} = box [string "PGconn *conn = uw_get_db(ctx);", newline, string "PGresult *res = PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 0);", newline, newline, - queryCommon {loc = loc, numCols = numCols, doCols = doCols, query = string "query"}] + queryCommon {loc = loc, cols = cols, doCols = doCols, query = string "query"}] fun p_ensql t e = case t of @@ -623,7 +635,7 @@ fun p_ensql t e = p_ensql t (box [string "(*", e, string ")"]), string ")"] -fun queryPrepared {loc, id, query, inputs, numCols, doCols} = +fun queryPrepared {loc, id, query, inputs, cols, doCols} = box [string "PGconn *conn = uw_get_db(ctx);", newline, string "const int paramFormats[] = { ", @@ -662,9 +674,9 @@ fun queryPrepared {loc, id, query, inputs, numCols, doCols} = string ", NULL, paramValues, paramLengths, paramFormats, 0);"], newline, newline, - queryCommon {loc = loc, numCols = numCols, doCols = doCols, query = box [string "\"", - string (String.toString query), - string "\""]}] + queryCommon {loc = loc, cols = cols, doCols = doCols, query = box [string "\"", + string (String.toString query), + string "\""]}] fun dmlCommon {loc, dml} = box [string "if (res == NULL) uw_error(ctx, FATAL, \"Out of memory allocating DML result.\");", @@ -821,6 +833,7 @@ val () = addDbms {name = "postgres", link = "-lpq", global_init = box [string "void uw_client_init() { }", newline], + p_sql_type = p_sql_type, init = init, query = query, queryPrepared = queryPrepared, diff --git a/src/settings.sig b/src/settings.sig index 5406d1de..14e6338d 100644 --- a/src/settings.sig +++ b/src/settings.sig @@ -112,7 +112,7 @@ signature SETTINGS = sig | Client | Nullable of sql_type - val p_sql_type : sql_type -> string + val p_sql_ctype : sql_type -> string val isBlob : sql_type -> bool val isNotNull : sql_type -> bool @@ -125,18 +125,19 @@ signature SETTINGS = sig (* Pass these linker arguments *) global_init : Print.PD.pp_desc, (* Define uw_client_init() *) + p_sql_type : sql_type -> string, init : {dbstring : string, prepared : (string * int) list, tables : (string * (string * sql_type) list) list, views : (string * (string * sql_type) list) list, sequences : string list} -> Print.PD.pp_desc, (* Define uw_db_init(), uw_db_close(), uw_db_begin(), uw_db_commit(), and uw_db_rollback() *) - query : {loc : ErrorMsg.span, numCols : int, + query : {loc : ErrorMsg.span, cols : sql_type list, doCols : ({wontLeakStrings : bool, col : int, typ : sql_type} -> Print.PD.pp_desc) -> Print.PD.pp_desc} -> Print.PD.pp_desc, queryPrepared : {loc : ErrorMsg.span, id : int, query : string, - inputs : sql_type list, numCols : int, + inputs : sql_type list, cols : sql_type list, doCols : ({wontLeakStrings : bool, col : int, typ : sql_type} -> Print.PD.pp_desc) -> Print.PD.pp_desc} -> Print.PD.pp_desc, diff --git a/src/settings.sml b/src/settings.sml index a242768f..f2c2461d 100644 --- a/src/settings.sml +++ b/src/settings.sml @@ -285,7 +285,7 @@ datatype sql_type = | Client | Nullable of sql_type -fun p_sql_type t = +fun p_sql_ctype t = let open Print.PD open Print @@ -300,7 +300,7 @@ fun p_sql_type t = | Channel => "uw_Basis_channel" | Client => "uw_Basis_client" | Nullable String => "uw_Basis_string" - | Nullable t => p_sql_type t ^ "*" + | Nullable t => p_sql_ctype t ^ "*" end fun isBlob Blob = true @@ -315,17 +315,18 @@ type dbms = { header : string, link : string, global_init : Print.PD.pp_desc, + p_sql_type : sql_type -> string, init : {dbstring : string, prepared : (string * int) list, tables : (string * (string * sql_type) list) list, views : (string * (string * sql_type) list) list, sequences : string list} -> Print.PD.pp_desc, - query : {loc : ErrorMsg.span, numCols : int, + query : {loc : ErrorMsg.span, cols : sql_type list, doCols : ({wontLeakStrings : bool, col : int, typ : sql_type} -> Print.PD.pp_desc) -> Print.PD.pp_desc} -> Print.PD.pp_desc, queryPrepared : {loc : ErrorMsg.span, id : int, query : string, - inputs : sql_type list, numCols : int, + inputs : sql_type list, cols : sql_type list, doCols : ({wontLeakStrings : bool, col : int, typ : sql_type} -> Print.PD.pp_desc) -> Print.PD.pp_desc} -> Print.PD.pp_desc, @@ -341,6 +342,7 @@ val curDb = ref ({name = "", header = "", link = "", global_init = Print.box [], + p_sql_type = fn _ => "", init = fn _ => Print.box [], query = fn _ => Print.box [], queryPrepared = fn _ => Print.box [], -- cgit v1.2.3 From e89607929e56988040d1b31e62362c16a9b75147 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 18 Jul 2009 13:46:22 -0400 Subject: New command-line options; describe simple SQLite build in demo intro --- demo/prose | 10 ++++++- doc/manual.tex | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 88 insertions(+), 6 deletions(-) (limited to 'doc') diff --git a/demo/prose b/demo/prose index 37b49a90..df0ed0a9 100644 --- a/demo/prose +++ b/demo/prose @@ -1,6 +1,6 @@

Ur/Web is a domain-specific language for programming web applications backed by SQL databases. It is (strongly) statically-typed (like ML and Haskell) and purely functional (like Haskell). Ur is the base language, and the web-specific features of Ur/Web (mostly) come only in the form of special rules for parsing and optimization. The Ur core looks a lot like Standard ML, with a few Haskell-isms added, and kinder, gentler versions added of many features from dependently-typed languages like the logic behind Coq. The type system is much more expressive than in ML and Haskell, such that well-typed web applications cannot "go wrong," not just in handling single HTTP requests, but across their entire lifetimes of interacting with HTTP clients. Beyond that, Ur is unusual in using ideas from dependent typing to enable very effective metaprogramming, or programming with explicit analysis of type structure. Many common web application components can be built by Ur/Web functions that operate on types, where it seems impossible to achieve similar code re-use in more established statically-typed languages.

-

This demo is built automatically from Ur/Web sources and supporting files. If you unpack the Ur/Web source distribution, then the following steps will build you a local version of this demo: +

This demo is built automatically from Ur/Web sources and supporting files. If you unpack the Ur/Web source distribution, then the following steps will (if you're lucky) build you a local version of this demo. If you're not lucky, you can consult the beginning of the manual for more detailed instructions.

./configure
 make
@@ -24,6 +24,14 @@ ProxyPassReverse /Demo/ http://localhost:8080/Demo/

Building the demo also generates a demo.sql file, giving the SQL commands to run to define all of the tables and sequences that the applications expect to see. The file demo.urp contains a database line with the PostgreSQL database that the demo web server will try to connect to.

+

The easiest way to get a demo running locally is probably with this alternate command sequence: + +

urweb -dbms sqlite -db /path/to/database/file -demo /Demo demo
+sqlite3 /path/to/database/file <demo/demo.sql
+demo/demo.exe

+ +

Then you can skip the static content and connect directly to the demo server at http://localhost:8080/Demo/Demo/main, which contains links to the individual demos.

+

The rest of the demo focuses on the individual applications. Follow the links in the lefthand frame to visit the applications, commentary, and syntax-highlighted source code. (An Emacs mode is behind the syntax highlighting.) I recommend visiting the applications in the order listed, since that is the order in which new concepts are introduced.

hello.urp diff --git a/doc/manual.tex b/doc/manual.tex index 3b57dc1b..c28d9610 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -56,24 +56,27 @@ make sudo make install \end{verbatim} -Some other packages must be installed for the above to work. At a minimum, you need a standard UNIX shell, with standard UNIX tools like sed and GCC in your execution path; MLton, the whole-program optimizing compiler for Standard ML; and the mhash C library. To build programs that access SQL databases, you also need libpq, the PostgreSQL client library. As of this writing, in the ``testing'' version of Debian Linux, this command will install the more uncommon of these dependencies: +Some other packages must be installed for the above to work. At a minimum, you need a standard UNIX shell, with standard UNIX tools like sed and GCC in your execution path; MLton, the whole-program optimizing compiler for Standard ML; and the mhash C library. As of this writing, in the ``testing'' version of Debian Linux, this command will install the more uncommon of these dependencies: +\begin{verbatim} +apt-get install mlton libmhash-dev +\end{verbatim} +To build programs that access SQL databases, you also need one of these client libraries for supported backends. \begin{verbatim} -apt-get install mlton libmhash-dev libpq-dev +apt-get install libpq-dev libmysqlclient15-dev libsqlite3-dev \end{verbatim} It is also possible to access the modules of the Ur/Web compiler interactively, within Standard ML of New Jersey. To install the prerequisites in Debian testing: - \begin{verbatim} apt-get install smlnj libsmlnj-smlnj ml-yacc ml-lpt \end{verbatim} To begin an interactive session with the Ur compiler modules, run \texttt{make smlnj}, and then, from within an \texttt{sml} session, run \texttt{CM.make "src/urweb.cm";}. The \texttt{Compiler} module is the main entry point. -To run an SQL-backed application, you will probably want to install the PostgreSQL server. Version 8.3 or higher is required. +To run an SQL-backed application with a backend besides SQLite, you will probably want to install one of these servers. \begin{verbatim} -apt-get install postgresql-8.3 +apt-get install postgresql-8.3 mysql-server-5.0 \end{verbatim} To use the Emacs mode, you must have a modern Emacs installed. We assume that you already know how to do this, if you're in the business of looking for an Emacs mode. The demo generation facility of the compiler will also call out to Emacs to syntax-highlight code, and that process depends on the \texttt{htmlize} module, which can be installed in Debian testing via: @@ -165,6 +168,77 @@ To time how long the different compiler phases run, without generating an execut urweb -timing P \end{verbatim} +Some other command-line parameters are accepted: +\begin{itemize} +\item \texttt{-db }: Set database connection information, using the format expected by Postgres's \texttt{PQconnectdb()}, which is \texttt{name1=value1 ... nameN=valueN}. The same format is also parsed and used to discover connection parameters for MySQL and SQLite. The only significant settings for MySQL are \texttt{host}, \texttt{hostaddr}, \texttt{port}, \texttt{dbname}, \texttt{user}, and \texttt{password}. The only significant setting for SQLite is \texttt{dbname}, which is interpreted as the filesystem path to the database. Additionally, when using SQLite, a database string may be just a file path. + +\item \texttt{-dbms [postgres|mysql|sqlite]}: Sets the database backend to use. + \begin{itemize} + \item \texttt{postgres}: This is PostgreSQL, the default. Among the supported engines, Postgres best matches the design philosophy behind Ur, with a focus on consistent views of data, even in the face of much concurrency. Different database engines have different quirks of SQL syntax. Ur/Web tends to use Postgres idioms where there are choices to be made, though the compiler translates SQL as needed to support other backends. + + A command sequence like this can initialize a Postgres database, using a file \texttt{app.sql} generated by the compiler: + \begin{verbatim} +createdb app +psql -f app.sql app + \end{verbatim} + + \item \texttt{mysql}: This is MySQL, another popular relational database engine that uses persistent server processes. Ur/Web needs transactions to function properly. Many installations of MySQL use non-transactional storage engines by default. Ur/Web generates table definitions that try to use MySQL's InnoDB engine, which supports transactions. You can edit the first line of a generated \texttt{.sql} file to change this behavior, but it really is true that Ur/Web applications will exhibit bizarre behavior if you choose an engine that ignores transaction commands. + + A command sequence like this can initialize a MySQL database: + \begin{verbatim} +echo "CREATE DATABASE app" | mysql +mysql -D app + (( "bin-path" => "/path/to/hello.exe", + "socket" => "/tmp/hello", + "check-local" => "disable", + "docroot" => "/", + "max-procs" => "1" + )) +) + \end{verbatim} + The least obvious requirement is setting \texttt{max-procs} to 1, so that lighttpd doesn't try to multiplex requests across multiple external processes. This is required for message-passing applications, where a single database of client connections is maintained within a multi-threaded server process. Multiple processes may, however, be used safely with applications that don't use message-passing. + + A FastCGI process reads the environment variable \texttt{URWEB\_NUM\_THREADS} to determine how many threads to spawn for handling client requests. The default is 1. + \end{itemize} + +\item \texttt{-sql FILENAME}: Set where a database set-up SQL script is written. +\end{itemize} + \section{Ur Syntax} -- cgit v1.2.3 From 19cd9e965929d541e6714f62154f01b9e487a712 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 18 Jul 2009 15:08:21 -0400 Subject: FFI manual section --- CHANGELOG | 9 +++++++ doc/manual.tex | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/cjr_print.sml | 10 ++++---- 3 files changed, 90 insertions(+), 5 deletions(-) (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index b9018446..5eeecef8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,12 @@ +======== +20090718 +======== + +- New application protocols: CGI and FastCGI +- New database backends: MySQL and SQLite +- More JavaScript events added to tags in standard library +- New manual section on using the foreign function interface (FFI) + ======== 20090623 ======== diff --git a/doc/manual.tex b/doc/manual.tex index c28d9610..cb3ce586 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1940,6 +1940,82 @@ The HTTP standard suggests that GET requests only be used in ways that generate Ur/Web includes a kind of automatic protection against cross site request forgery attacks. Whenever any page execution can have side effects and can also read at least one cookie value, all cookie values must be signed cryptographically, to ensure that the user has come to the current page by submitting a form on a real page generated by the proper server. Signing and signature checking are inserted automatically by the compiler. This prevents attacks like phishing schemes where users are directed to counterfeit pages with forms that submit to your application, where a user's cookies might be submitted without his knowledge, causing some undesired side effect. +\section{The Foreign Function Interface} + +It is possible to call your own C and JavaScript code from Ur/Web applications, via the foreign function interface (FFI). The starting point for a new binding is a \texttt{.urs} signature file that presents your external library as a single Ur/Web module (with no nested modules). Compilation conventions map the types and values that you use into C and/or JavaScript types and values. + +It is most convenient to encapsulate an FFI binding with a new \texttt{.urp} file, which applications can include with the \texttt{library} directive in their own \texttt{.urp} files. A number of directives are likely to show up in the library's project file. + +\begin{itemize} +\item \texttt{clientOnly Module.ident} registers a value as being allowed only in client-side code. +\item \texttt{clientToServer Module.ident} declares a type as OK to marshal between clients and servers. By default, abstract FFI types are not allowed to be marshalled, since your library might be maintaining invariants that the simple serialization code doesn't check. +\item \texttt{effectful Module.ident} registers a function that can have side effects. It is important to remember to use this directive for each such function, or else the optimizer might change program semantics. +\item \texttt{ffi FILE.urs} names the file giving your library's signature. You can include multiple such files in a single \texttt{.urp} file, and each file \texttt{mod.urp} defines an FFI module \texttt{Mod}. +\item \texttt{header FILE} requests inclusion of a C header file. +\item \texttt{jsFunc Module.ident=name} gives a mapping from an Ur name for a value to a JavaScript name. +\item \texttt{link FILE} requests that \texttt{FILE} be linked into applications. It should be a C object or library archive file, and you are responsible for generating it with your own build process. +\item \texttt{script URL} requests inclusion of a JavaScript source file within application HTML. +\item \texttt{serverOnly Module.ident} registers a value as being allowed only in server-side code. +\end{itemize} + +\subsection{Writing C FFI Code} + +A server-side FFI type or value \texttt{Module.ident} must have a corresponding type or value definition \texttt{uw\_Module\_ident} in C code. With the current Ur/Web version, it's not generally possible to work with Ur records or complex datatypes in C code, but most other kinds of types are fair game. + +\begin{itemize} + \item Primitive types defined in \texttt{Basis} are themselves using the standard FFI interface, so you may refer to them like \texttt{uw\_Basis\_t}. See \texttt{include/types.h} for their definitions. + \item Enumeration datatypes, which have only constructors that take no arguments, should be defined using C \texttt{enum}s. The type is named as for any other type identifier, and each constructor \texttt{c} gets an enumeration constant named \texttt{uw\_Module\_c}. + \item A datatype \texttt{dt} (such as \texttt{Basis.option}) that has one non-value-carrying constructor \texttt{NC} and one value-carrying constructor \texttt{C} gets special treatment. Where \texttt{T} is the type of \texttt{C}'s argument, and where we represent \texttt{T} as \texttt{t} in C, we represent \texttt{NC} with \texttt{NULL}. The representation of \texttt{C} depends on whether we're sure that we don't need to use \texttt{NULL} to represent \texttt{t} values; this condition holds only for strings and complex datatypes. For such types, \texttt{C v} is represented with the C encoding of \texttt{v}, such that the translation of \texttt{dt} is \texttt{t}. For other types, \texttt{C v} is represented with a pointer to the C encoding of v, such that the translation of \texttt{dt} is \texttt{t*}. +\end{itemize} + +The C FFI version of a Ur function with type \texttt{T1 -> ... -> TN -> R} or \texttt{T1 -> ... -> TN -> transaction R} has a C prototype like \texttt{R uw\_Module\_ident(uw\_context, T1, ..., TN)}. Only functions with types of the second form may have side effects. \texttt{uw\_context} is the type of state that persists across handling a client request. Many functions that operate on contexts are prototyped in \texttt{include/urweb.h}. Most should only be used internally by the compiler. A few are useful in general FFI implementation: +\begin{itemize} + \item \begin{verbatim} +void uw_error(uw_context, failure_kind, const char *fmt, ...); + \end{verbatim} + Abort the current request processing, giving a \texttt{printf}-style format string and arguments for generating an error message. The \texttt{failure\_kind} argument can be \texttt{FATAL}, to abort the whole execution; \texttt{BOUNDED\_RETRY}, to try processing the request again from the beginning, but failing if this happens too many times; or \texttt{UNLIMITED\_RETRY}, to repeat processing, with no cap on how many times this can recur. + + \item \begin{verbatim} +void uw_push_cleanup(uw_context, void (*func)(void *), void *arg); +void uw_pop_cleanup(uw_context); + \end{verbatim} + Manipulate a stack of actions that should be taken if any kind of error condition arises. Calling the ``pop'' function both removes an action from the stack and executes it. + + \item \begin{verbatim} +void *uw_malloc(uw_context, size_t); + \end{verbatim} + A version of \texttt{malloc()} that allocates memory inside a context's heap, which is managed with region allocation. Thus, there is no \texttt{uw\_free()}, but you need to be careful not to keep ad-hoc C pointers to this area of memory. + + For performance and correctness reasons, it is usually preferable to use \texttt{uw\_malloc()} instead of \texttt{malloc()}. The former manipulates a local heap that can be kept allocated across page requests, while the latter uses global data structures that may face contention during concurrent execution. + + \item \begin{verbatim} +typedef void (*uw_callback)(void *); +void uw_register_transactional(uw_context, void *data, uw_callback commit, + uw_callback rollback, uw_callback free); + \end{verbatim} + All side effects in Ur/Web programs need to be compatible with transactions, such that any set of actions can be undone at any time. Thus, you should not perform actions with non-local side effects directly; instead, register handlers to be called when the current transaction is committed or rolled back. The arguments here give an arbitary piece of data to be passed to callbacks, a function to call on commit, a function to call on rollback, and a function to call afterward in either case to clean up any allocated resources. A rollback handler may be called after the associated commit handler has already been called, if some later part of the commit process fails. + + To accommodate some stubbornly non-transactional real-world actions like sending an e-mail message, Ur/Web allows the \texttt{rollback} parameter to be \texttt{NULL}. When a transaction commits, all \texttt{commit} actions that have non-\texttt{NULL} rollback actions are tried before any \texttt{commit} actions that have \texttt{NULL} rollback actions. Thus, if a single execution uses only one non-transactional action, and if that action never fails partway through its execution while still causing an observable side effect, then Ur/Web can maintain the transactional abstraction. +\end{itemize} + + +\subsection{Writing JavaScript FFI Code} + +JavaScript is dynamically typed, so Ur/Web type definitions imply no JavaScript code. The JavaScript identifier for each FFI function is set with the \texttt{jsFunc} directive. Each identifier can be defined in any JavaScript file that you ask to include with the \texttt{script} directive. + +In contrast to C FFI code, JavaScript FFI functions take no extra context argument. Their argument lists are as you would expect from their Ur types. Only functions whose ranges take the form \texttt{transaction T} should have side effects; the JavaScript ``return type'' of such a function is \texttt{T}. Here are the conventions for representing Ur values in JavaScript. + +\begin{itemize} +\item Integers, floats, strings, characters, and booleans are represented in the usual JavaScript way. +\item Ur functions are represented with JavaScript functions, currying and all. Only named FFI functions are represented with multiple JavaScript arguments. +\item An Ur record is represented with a JavaScript record, where Ur field name \texttt{N} translates to JavaScript field name \texttt{\_N}. An exception to this rule is that the empty record is encoded as \texttt{null}. +\item \texttt{option}-like types receive special handling similar to their handling in C. The ``\texttt{None}'' constructor is \texttt{null}, and a use of the ``\texttt{Some}'' constructor on a value \texttt{v} is either \texttt{v}, if the underlying type doesn't need to use \texttt{null}; or \texttt{\{v:v\}} otherwise. +\item Any other datatypes represent a non-value-carrying constructor \texttt{C} as \texttt{"\_C"} and an application of a constructor \texttt{C} to value \texttt{v} as \texttt{\{n:"\_C", v:v\}}. This rule only applies to datatypes defined in FFI module signatures; the compiler is free to optimize the representations of other, non-\texttt{option}-like datatypes in arbitrary ways. +\end{itemize} + +It is possible to write JavaScript FFI code that interacts with the functional-reactive structure of a document, but this version of the manual doesn't cover the details. + + \section{Compiler Phases} The Ur/Web compiler is unconventional in that it relies on a kind of \emph{heuristic compilation}. Not all valid programs will compile successfully. Informally, programs fail to compile when they are ``too higher order.'' Compiler phases do their best to eliminate different kinds of higher order-ness, but some programs just won't compile. This is a trade-off for producing very efficient executables. Compiled Ur/Web programs use native C representations and require no garbage collection. diff --git a/src/cjr_print.sml b/src/cjr_print.sml index eccd60c2..83b49719 100644 --- a/src/cjr_print.sml +++ b/src/cjr_print.sml @@ -85,11 +85,11 @@ fun p_typ' par env (t, loc) = (case ListUtil.search #3 (!xncs) of NONE => raise Fail "CjrPrint: TDatatype marked Option has no constructor with an argument" | SOME t => - case #1 t of - TDatatype _ => p_typ' par env t - | TFfi ("Basis", "string") => p_typ' par env t - | _ => box [p_typ' par env t, - string "*"]) + if isUnboxable t then + p_typ' par env t + else + box [p_typ' par env t, + string "*"]) | TDatatype (Default, n, _) => (box [string "struct", space, -- cgit v1.2.3 From 87777778aeaee3e1de767499f86bc22789118a69 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 25 Aug 2009 17:33:13 -0400 Subject: New release --- CHANGELOG | 8 ++++++ Makefile.in | 2 +- doc/manual.tex | 8 ++++++ include/types.h | 1 + src/c/memmem.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/compiler.sml | 5 ++-- 6 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 src/c/memmem.c (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index 5eeecef8..c67676f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +======== +20090825 +======== + +- Many bug fixes +- Remote procedure calls must be marked with the new 'rpc' function. +- Some tweaks to enable usage on OSX (suggested by Paul Snively) + ======== 20090718 ======== diff --git a/Makefile.in b/Makefile.in index 1aca9590..0d8b998a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -14,7 +14,7 @@ all: smlnj mlton c smlnj: src/urweb.cm mlton: bin/urweb -OBJS := urweb request queue http cgi fastcgi +OBJS := urweb request queue http cgi fastcgi memmem c: $(OBJS:%=lib/c/%.o) clean: diff --git a/doc/manual.tex b/doc/manual.tex index cb3ce586..f1f9c967 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1795,6 +1795,14 @@ $$\begin{array}{l} Transactions can be run on the client by including them in attributes like the $\mt{Onclick}$ attribute of $\mt{button}$, and GUI widgets like $\mt{ctextbox}$ have $\mt{Source}$ attributes that can be used to connect them to sources, so that their values can be read by code running because of, e.g., an $\mt{Onclick}$ event. +\subsubsection{Remote Procedure Calls} + +Any function call may be made a client-to-server ``remote procedure call'' if the function being called needs no features that are only available to client code. To make a function call an RPC, pass that function call as the argument to $\mt{Basis.rpc}$: + +$$\begin{array}{l} + \mt{val} \; \mt{rpc} : \mt{t} ::: \mt{Type} \to \mt{transaction} \; \mt{t} \to \mt{transaction} \; \mt{t} +\end{array}$$ + \subsubsection{Asynchronous Message-Passing} To support asynchronous, ``server push'' delivery of messages to clients, any client that might need to receive an asynchronous message is assigned a unique ID. These IDs may be retrieved both on the client and on the server, during execution of code related to a client. diff --git a/include/types.h b/include/types.h index 062888af..19eae5ad 100644 --- a/include/types.h +++ b/include/types.h @@ -2,6 +2,7 @@ #define URWEB_TYPES_H #include +#include typedef long long uw_Basis_int; typedef double uw_Basis_float; diff --git a/src/c/memmem.c b/src/c/memmem.c new file mode 100644 index 00000000..e0687a28 --- /dev/null +++ b/src/c/memmem.c @@ -0,0 +1,83 @@ +/* $NetBSD$ */ + +/*- + * Copyright (c) 2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#if defined(LIBC_SCCS) && !defined(lint) +__RCSID("$NetBSD$"); +#endif /* LIBC_SCCS and not lint */ + +#if !defined(_KERNEL) && !defined(_STANDALONE) +#include +#include +#else +#include +#define _DIAGASSERT(x) (void)0 +#define NULL ((char *)0) +#endif + +/* + * memmem() returns the location of the first occurence of data + * pattern b2 of size len2 in memory block b1 of size len1 or + * NULL if none is found. + */ +void * +memmem(const void *b1, const void *b2, size_t len1, size_t len2) +{ + /* Initialize search pointer */ + char *sp = (char *) b1; + + /* Initialize pattern pointer */ + char *pp = (char *) b2; + + /* Intialize end of search address space pointer */ + char *eos = sp + len1 - len2; + + /* Sanity check */ + if(!(b1 && b2 && len1 && len2)) + return NULL; + + while (sp <= eos) { + if (*sp == *pp) + if (memcmp(sp, pp, len2) == 0) + return sp; + + sp++; + } + + return NULL; +} diff --git a/src/compiler.sml b/src/compiler.sml index c99c0eeb..b7550fed 100644 --- a/src/compiler.sml +++ b/src/compiler.sml @@ -901,11 +901,12 @@ fun compileC {cname, oname, ename, libs, profile, debug, link = link'} = let val proto = Settings.currentProtocol () val urweb_o = clibFile "urweb.o" + val memmem_o = clibFile "memmem.o" val compile = "gcc " ^ Config.gccArgs ^ " -Wstrict-prototypes -Werror -O3 -I " ^ Config.includ ^ " -c " ^ cname ^ " -o " ^ oname - val link = "gcc -Werror -O3 -lm -lmhash -pthread " ^ libs ^ " " ^ urweb_o ^ " " ^ oname - ^ " " ^ #link proto ^ " -o " ^ ename + val link = "gcc -Werror -O3 -lm -lmhash -pthread " ^ Config.gccArgs ^ " " ^ libs ^ " " ^ urweb_o ^ " " ^ oname + ^ " " ^ memmem_o ^ " " ^ #link proto ^ " -o " ^ ename val (compile, link) = if profile then -- cgit v1.2.3 From 9964a6181187df8d3ab19a057d9185fc9a88d789 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 26 Sep 2009 12:57:01 -0400 Subject: New release --- CHANGELOG | 9 +++++++++ doc/manual.tex | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index a5a11e6c..286ce5cc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,12 @@ +======== +20090926 +======== + +- Reimplemented client-side code generation to use an interpreter, rather than + compilation to JavaScript; this avoids common browser flaws: lack of + optimization of tail calls and occasional bugs in closure handling. +- Bug fixes + ======== 20090919 ======== diff --git a/doc/manual.tex b/doc/manual.tex index f1f9c967..3a532fbc 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -2015,10 +2015,10 @@ In contrast to C FFI code, JavaScript FFI functions take no extra context argume \begin{itemize} \item Integers, floats, strings, characters, and booleans are represented in the usual JavaScript way. -\item Ur functions are represented with JavaScript functions, currying and all. Only named FFI functions are represented with multiple JavaScript arguments. +\item Ur functions are represented in an unspecified way. This means that you should not rely on any details of function representation. Named FFI functions are represented as JavaScript functions with as many arguments as their Ur types specify. To call a non-FFI function \texttt{f} on argument \texttt{x}, run \texttt{execF(f, x)}. \item An Ur record is represented with a JavaScript record, where Ur field name \texttt{N} translates to JavaScript field name \texttt{\_N}. An exception to this rule is that the empty record is encoded as \texttt{null}. \item \texttt{option}-like types receive special handling similar to their handling in C. The ``\texttt{None}'' constructor is \texttt{null}, and a use of the ``\texttt{Some}'' constructor on a value \texttt{v} is either \texttt{v}, if the underlying type doesn't need to use \texttt{null}; or \texttt{\{v:v\}} otherwise. -\item Any other datatypes represent a non-value-carrying constructor \texttt{C} as \texttt{"\_C"} and an application of a constructor \texttt{C} to value \texttt{v} as \texttt{\{n:"\_C", v:v\}}. This rule only applies to datatypes defined in FFI module signatures; the compiler is free to optimize the representations of other, non-\texttt{option}-like datatypes in arbitrary ways. +\item Any other datatypes represent a non-value-carrying constructor \texttt{C} as \texttt{"C"} and an application of a constructor \texttt{C} to value \texttt{v} as \texttt{\{n:"C", v:v\}}. This rule only applies to datatypes defined in FFI module signatures; the compiler is free to optimize the representations of other, non-\texttt{option}-like datatypes in arbitrary ways. \end{itemize} It is possible to write JavaScript FFI code that interacts with the functional-reactive structure of a document, but this version of the manual doesn't cover the details. -- cgit v1.2.3 From 82ed38468f5da48ce6e9f6ec336cf5b11ca4bb4d Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 6 Oct 2009 15:59:11 -0400 Subject: Initial versioned1 demo working --- CHANGELOG | 1 + doc/manual.tex | 5 +++-- src/c/urweb.c | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index d75b807c..0257d617 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ Next - Bug fixes - Improvement to choice of line number to cite in record unification error messages +- SELECT DISTINCT ======== 20090926 diff --git a/doc/manual.tex b/doc/manual.tex index 3a532fbc..5a46552d 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1469,7 +1469,8 @@ $$\begin{array}{l} \hspace{.1in} \to \mt{grouped} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedFields} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedExps} ::: \{\mt{Type}\} \\ - \hspace{.1in} \to \{\mt{From} : \mt{sql\_from\_items} \; \mt{tables}, \\ + \hspace{.1in} \to \{\mt{Distinct} : \mt{bool}, \\ + \hspace{.2in} \mt{From} : \mt{sql\_from\_items} \; \mt{tables}, \\ \hspace{.2in} \mt{Where} : \mt{sql\_exp} \; \mt{tables} \; [] \; [] \; \mt{bool}, \\ \hspace{.2in} \mt{GroupBy} : \mt{sql\_subset} \; \mt{tables} \; \mt{grouped}, \\ \hspace{.2in} \mt{Having} : \mt{sql\_exp} \; \mt{grouped} \; \mt{tables} \; [] \; \mt{bool}, \\ @@ -1855,7 +1856,7 @@ Queries $Q$ are added to the rules for expressions $e$. $$\begin{array}{rrcll} \textrm{Queries} & Q &::=& (q \; [\mt{ORDER} \; \mt{BY} \; (E \; [o],)^+] \; [\mt{LIMIT} \; N] \; [\mt{OFFSET} \; N]) \\ - \textrm{Pre-queries} & q &::=& \mt{SELECT} \; P \; \mt{FROM} \; T,^+ \; [\mt{WHERE} \; E] \; [\mt{GROUP} \; \mt{BY} \; p,^+] \; [\mt{HAVING} \; E] \\ + \textrm{Pre-queries} & q &::=& \mt{SELECT} \; [\mt{DISTINCT}] \; P \; \mt{FROM} \; T,^+ \; [\mt{WHERE} \; E] \; [\mt{GROUP} \; \mt{BY} \; p,^+] \; [\mt{HAVING} \; E] \\ &&& \mid q \; R \; q \\ \textrm{Relational operators} & R &::=& \mt{UNION} \mid \mt{INTERSECT} \mid \mt{EXCEPT} \end{array}$$ diff --git a/src/c/urweb.c b/src/c/urweb.c index 88e9569d..e49de568 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -2160,7 +2160,6 @@ char *uw_Basis_sqlifyTime(uw_context ctx, uw_Basis_time t) { if (localtime_r(&t, &stm)) { s = uw_malloc(ctx, TIMES_MAX); - --stm.tm_hour; len = strftime(s, TIMES_MAX, TIME_FMT, &stm); r = uw_malloc(ctx, len + 14); sprintf(r, "'%s'::timestamp", s); -- cgit v1.2.3 From c1816939cb921097620b88c213d181d0bdba7f29 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 26 Nov 2009 14:20:00 -0500 Subject: More fun with cookies --- CHANGELOG | 7 +++++++ demo/cookie.ur | 27 +++++++++++++++++++++++++-- doc/manual.tex | 3 ++- include/urweb.h | 4 +++- lib/ur/basis.urs | 6 +++++- src/c/urweb.c | 29 +++++++++++++++++++++++++++-- src/demo.sml | 2 +- src/monoize.sml | 34 +++++++++++++++++++++++++++++----- src/settings.sml | 1 + 9 files changed, 100 insertions(+), 13 deletions(-) (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index 33f9bc9c..25a9b851 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +======== +Next +======== + +- Extended cookie interface (breaks backward compatibility for 'setCookie') +- Bug fixes + ======== 20091124 ======== diff --git a/demo/cookie.ur b/demo/cookie.ur index ad4e19ec..7e011157 100644 --- a/demo/cookie.ur +++ b/demo/cookie.ur @@ -1,15 +1,30 @@ cookie c : {A : string, B : float, C : int} fun set r = - setCookie c {A = r.A, B = readError r.B, C = readError r.C}; + setCookie c {Value = {A = r.A, B = readError r.B, C = readError r.C}, + Expires = None, + Secure = False}; return Cookie set. +fun setExp r = + setCookie c {Value = {A = r.A, B = readError r.B, C = readError r.C}, + Expires = Some (readError "2012-11-6 00:00:00"), + Secure = False}; + return Cookie set robustly. + +fun delete () = + clearCookie c; + return Cookie cleared. + fun main () = ro <- getCookie c; return {case ro of None => No cookie set. - | Some v => Cookie: A = {[v.A]}, B = {[v.B]}, C = {[v.C]}} + | Some v => + Cookie: A = {[v.A]}, B = {[v.B]}, C = {[v.C]}
+
+
}

@@ -17,5 +32,13 @@ fun main () = B:
C:
+
+ +
+ Version that expires on November 6, 2012:
+ A:
+ B:
+ C:
+
diff --git a/doc/manual.tex b/doc/manual.tex index 5a46552d..866b9585 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1288,7 +1288,8 @@ $$\begin{array}{l} \\ \mt{con} \; \mt{http\_cookie} :: \mt{Type} \to \mt{Type} \\ \mt{val} \; \mt{getCookie} : \mt{t} ::: \mt{Type} \to \mt{http\_cookie} \; \mt{t} \to \mt{transaction} \; (\mt{option} \; \mt{t}) \\ - \mt{val} \; \mt{setCookie} : \mt{t} ::: \mt{Type} \to \mt{http\_cookie} \; \mt{t} \to \mt{t} \to \mt{transaction} \; \mt{unit} + \mt{val} \; \mt{setCookie} : \mt{t} ::: \mt{Type} \to \mt{http\_cookie} \; \mt{t} \to \{\mt{Value} : \mt{t}, \mt{Expires} : \mt{option} \; \mt{time}, \mt{Secure} : \mt{bool}\} \to \mt{transaction} \; \mt{unit} \\ + \mt{val} \; \mt{clearCookie} : \mt{t} ::: \mt{Type} \to \mt{http\_cookie} \; \mt{t} \to \mt{transaction} \; \mt{unit} \end{array}$$ There are also an abstract $\mt{url}$ type and functions for converting to it, based on the policy defined by \texttt{[allow|deny] url} directives in the project file. diff --git a/include/urweb.h b/include/urweb.h index 55068966..9884a3ca 100644 --- a/include/urweb.h +++ b/include/urweb.h @@ -185,7 +185,8 @@ uw_Basis_string uw_Basis_requestHeader(uw_context, uw_Basis_string); void uw_write_header(uw_context, uw_Basis_string); uw_Basis_string uw_Basis_get_cookie(uw_context, uw_Basis_string c); -uw_unit uw_Basis_set_cookie(uw_context, uw_Basis_string prefix, uw_Basis_string c, uw_Basis_string v); +uw_unit uw_Basis_set_cookie(uw_context, uw_Basis_string prefix, uw_Basis_string c, uw_Basis_string v, uw_Basis_time *expires, uw_Basis_bool secure); +uw_unit uw_Basis_clear_cookie(uw_context, uw_Basis_string prefix, uw_Basis_string c); uw_Basis_channel uw_Basis_new_channel(uw_context, uw_unit); uw_unit uw_Basis_send(uw_context, uw_Basis_channel, uw_Basis_string); @@ -210,6 +211,7 @@ uw_Basis_int uw_Basis_blobSize(uw_context, uw_Basis_blob); __attribute__((noreturn)) void uw_return_blob(uw_context, uw_Basis_blob, uw_Basis_string mimeType); uw_Basis_time uw_Basis_now(uw_context); +extern const uw_Basis_time uw_Basis_minTime; void uw_register_transactional(uw_context, void *data, uw_callback commit, uw_callback rollback, uw_callback free); diff --git a/lib/ur/basis.urs b/lib/ur/basis.urs index 47bc3d48..31aa4cdd 100644 --- a/lib/ur/basis.urs +++ b/lib/ur/basis.urs @@ -115,6 +115,7 @@ val current : t ::: Type -> signal t -> transaction t (** * Time *) val now : transaction time +val minTime : time (** HTTP operations *) @@ -123,7 +124,10 @@ val requestHeader : string -> transaction (option string) con http_cookie :: Type -> Type val getCookie : t ::: Type -> http_cookie t -> transaction (option t) -val setCookie : t ::: Type -> http_cookie t -> t -> transaction unit +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 (** JavaScript-y gadgets *) diff --git a/src/c/urweb.c b/src/c/urweb.c index 344ef2ad..cbe065c3 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -672,7 +672,7 @@ static input *check_input_space(uw_context ctx, size_t len) { } int uw_set_input(uw_context ctx, const char *name, char *value) { - printf("Input name %s\n", name); + //printf("Input name %s\n", name); if (!strcasecmp(name, ".b")) { int n = uw_input_num(value); @@ -2680,18 +2680,41 @@ uw_Basis_string uw_Basis_get_cookie(uw_context ctx, uw_Basis_string c) { return NULL; } -uw_unit uw_Basis_set_cookie(uw_context ctx, uw_Basis_string prefix, uw_Basis_string c, uw_Basis_string v) { +uw_unit uw_Basis_set_cookie(uw_context ctx, uw_Basis_string prefix, uw_Basis_string c, uw_Basis_string v, uw_Basis_time *expires, uw_Basis_bool secure) { uw_write_header(ctx, "Set-Cookie: "); uw_write_header(ctx, c); uw_write_header(ctx, "="); uw_write_header(ctx, v); uw_write_header(ctx, "; path="); uw_write_header(ctx, prefix); + if (expires) { + char formatted[30]; + struct tm tm; + + gmtime_r(expires, &tm); + + strftime(formatted, sizeof formatted, "%a, %d-%b-%Y %T GMT", &tm); + + uw_write_header(ctx, "; expires="); + uw_write_header(ctx, formatted); + } + if (secure) + uw_write_header(ctx, "; secure"); uw_write_header(ctx, "\r\n"); return uw_unit_v; } +uw_unit uw_Basis_clear_cookie(uw_context ctx, uw_Basis_string prefix, uw_Basis_string c) { + uw_write_header(ctx, "Set-Cookie: "); + uw_write_header(ctx, c); + uw_write_header(ctx, "=; path="); + uw_write_header(ctx, prefix); + uw_write_header(ctx, "; expires=Mon, 01-01-1970 00:00:00 GMT\r\n"); + + return uw_unit_v; +} + static delta *allocate_delta(uw_context ctx, unsigned client) { unsigned i; delta *d; @@ -3077,6 +3100,8 @@ uw_Basis_string uw_Basis_mstrcat(uw_context ctx, ...) { return r; } +const uw_Basis_time minTime = 0; + uw_Basis_time uw_Basis_now(uw_context ctx) { return time(NULL); } diff --git a/src/demo.sml b/src/demo.sml index 4e2caa99..c5480a93 100644 --- a/src/demo.sml +++ b/src/demo.sml @@ -430,7 +430,7 @@ fun make {prefix, dirname, guided} = TextIO.closeOut outf; - Compiler.compile (OS.Path.base fname) + Compiler.compiler (OS.Path.base fname) end; TextIO.output (demosOut, "\n\n"); diff --git a/src/monoize.sml b/src/monoize.sml index 5ac9d46b..25b7d9c3 100644 --- a/src/monoize.sml +++ b/src/monoize.sml @@ -1338,19 +1338,43 @@ fun monoExp (env, st, fm) (all as (e, loc)) = val s = (L'.TFfi ("Basis", "string"), loc) val un = (L'.TRecord [], loc) val t = monoType env t - val (e, fm) = urlifyExp env fm ((L'.ERel 1, loc), t) + val rt = (L'.TRecord [("Value", t), + ("Expires", (L'.TOption (L'.TFfi ("Basis", "time"), + loc), loc)), + ("Secure", (L'.TFfi ("Basis", "bool"), loc))], loc) + + fun fd x = (L'.EField ((L'.ERel 1, loc), x), loc) + val (e, fm) = urlifyExp env fm (fd "Value", t) in - ((L'.EAbs ("c", s, (L'.TFun (t, (L'.TFun (un, un), loc)), loc), - (L'.EAbs ("v", t, (L'.TFun (un, un), loc), + ((L'.EAbs ("c", s, (L'.TFun (rt, (L'.TFun (un, un), loc)), loc), + (L'.EAbs ("r", rt, (L'.TFun (un, un), loc), (L'.EAbs ("_", un, un, (L'.EFfiApp ("Basis", "set_cookie", [(L'.EPrim (Prim.String (Settings.getUrlPrefix ())), loc), (L'.ERel 2, loc), - e]), loc)), + e, + fd "Expires", + fd "Secure"]) + , loc)), loc)), loc)), loc), + fm) + end + + | L.ECApp ((L.EFfi ("Basis", "clearCookie"), _), t) => + let + val s = (L'.TFfi ("Basis", "string"), loc) + val un = (L'.TRecord [], loc) + in + ((L'.EAbs ("c", s, (L'.TFun (un, un), loc), + (L'.EAbs ("_", un, un, + (L'.EFfiApp ("Basis", "clear_cookie", + [(L'.EPrim (Prim.String + (Settings.getUrlPrefix ())), + loc), + (L'.ERel 1, loc)]), loc)), loc)), loc), fm) - end + end | L.ECApp ((L.EFfi ("Basis", "channel"), _), t) => ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "channel"), loc), diff --git a/src/settings.sml b/src/settings.sml index 300bbf2c..009e2b0a 100644 --- a/src/settings.sml +++ b/src/settings.sml @@ -80,6 +80,7 @@ fun mayClientToServer x = S.member (!clientToServer, x) val effectfulBase = basis ["dml", "nextval", "set_cookie", + "clear_cookie", "new_client_source", "get_client_source", "set_client_source", -- cgit v1.2.3 From b1c0d23525d6a0e8080ce973492c48628c39c717 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 24 Dec 2009 09:56:09 -0500 Subject: Updating the manual --- doc/manual.tex | 46 ++++++++++++++++++++++++++++++++++++++-------- include/urweb.h | 2 +- lib/ur/basis.urs | 2 +- 3 files changed, 40 insertions(+), 10 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 866b9585..11d1b2d9 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -425,6 +425,7 @@ $$\begin{array}{rrcll} &&& \mt{cookie} \; x : \tau & \textrm{HTTP cookie} \\ &&& \mt{style} \; x : \tau & \textrm{CSS class} \\ &&& \mt{class} \; x :: \kappa = c & \textrm{concrete constructor class} \\ + &&& \mt{task} \; e = e & \textrm{recurring task} \\ \\ \textrm{Modules} & M &::=& \mt{struct} \; d^* \; \mt{end} & \textrm{constant} \\ &&& X & \textrm{variable} \\ @@ -894,6 +895,11 @@ $$\infer{\Gamma \vdash \mt{cookie} \; x : \tau \leadsto \Gamma, x : \mt{Basis}.\ } \quad \infer{\Gamma \vdash \mt{style} \; x \leadsto \Gamma, x : \mt{Basis}.\mt{css\_class}}{}$$ +$$\infer{\Gamma \vdash \mt{task} \; e_1 = e_2 \leadsto \Gamma}{ + \Gamma \vdash e_1 :: \mt{Basis}.\mt{task\_kind} + & \Gamma \vdash e_2 :: \mt{Basis}.\mt{transaction} \; \{\} +}$$ + $$\infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leadsto \Gamma, x :: \kappa = c}{ \Gamma \vdash c :: \kappa }$$ @@ -1300,6 +1306,17 @@ $$\begin{array}{l} \end{array}$$ $\mt{bless}$ raises a runtime error if the string passed to it fails the URL policy. +It is possible to grab the current page's URL or to build a URL for an arbitrary transaction that would also be an acceptable value of a \texttt{link} attribute of the \texttt{a} tag. +$$\begin{array}{l} + \mt{val} \; \mt{currentUrl} : \mt{transaction} \; \mt{url} \\ + \mt{val} \; \mt{url} : \mt{transaction} \; \mt{page} \to \mt{url} +\end{array}$$ + +Page generation may be interrupted at any time with a request to redirect to a particular URL instead. +$$\begin{array}{l} + \mt{val} \; \mt{redirect} : \mt{t} ::: \mt{Type} \to \mt{url} \to \mt{transaction} \; \mt{t} +\end{array}$$ + It's possible for pages to return files of arbitrary MIME types. A file can be input from the user using this data type, along with the $\mt{upload}$ form tag. $$\begin{array}{l} \mt{type} \; \mt{file} \\ @@ -1470,12 +1487,14 @@ $$\begin{array}{l} \hspace{.1in} \to \mt{grouped} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedFields} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedExps} ::: \{\mt{Type}\} \\ - \hspace{.1in} \to \{\mt{Distinct} : \mt{bool}, \\ + \hspace{.1in} \to \mt{empties} :: \{\mt{Unit}\} \\ + \hspace{.1in} \to [\mt{empties} \sim \mt{selectedFields}] \\ + \hspace{.1in} \Rightarrow \{\mt{Distinct} : \mt{bool}, \\ \hspace{.2in} \mt{From} : \mt{sql\_from\_items} \; \mt{tables}, \\ \hspace{.2in} \mt{Where} : \mt{sql\_exp} \; \mt{tables} \; [] \; [] \; \mt{bool}, \\ \hspace{.2in} \mt{GroupBy} : \mt{sql\_subset} \; \mt{tables} \; \mt{grouped}, \\ \hspace{.2in} \mt{Having} : \mt{sql\_exp} \; \mt{grouped} \; \mt{tables} \; [] \; \mt{bool}, \\ - \hspace{.2in} \mt{SelectFields} : \mt{sql\_subset} \; \mt{grouped} \; \mt{selectedFields}, \\ + \hspace{.2in} \mt{SelectFields} : \mt{sql\_subset} \; \mt{grouped} \; (\mt{map} \; (\lambda \_ \Rightarrow []) \; \mt{empties} \rc \mt{selectedFields}), \\ \hspace{.2in} \mt {SelectExps} : \$(\mt{map} \; (\mt{sql\_exp} \; \mt{grouped} \; \mt{tables} \; []) \; \mt{selectedExps}) \} \\ \hspace{.1in} \to \mt{sql\_query1} \; \mt{tables} \; \mt{selectedFields} \; \mt{selectedExps} \end{array}$$ @@ -1683,7 +1702,8 @@ SQL sequences are counters with concurrency control, often used to assign unique $$\begin{array}{l} \mt{type} \; \mt{sql\_sequence} \\ - \mt{val} \; \mt{nextval} : \mt{sql\_sequence} \to \mt{transaction} \; \mt{int} + \mt{val} \; \mt{nextval} : \mt{sql\_sequence} \to \mt{transaction} \; \mt{int} \\ + \mt{val} \; \mt{setval} : \mt{sql\_sequence} \to \mt{int} \to \mt{transaction} \; \mt{unit} \end{array}$$ @@ -1857,8 +1877,8 @@ Queries $Q$ are added to the rules for expressions $e$. $$\begin{array}{rrcll} \textrm{Queries} & Q &::=& (q \; [\mt{ORDER} \; \mt{BY} \; (E \; [o],)^+] \; [\mt{LIMIT} \; N] \; [\mt{OFFSET} \; N]) \\ - \textrm{Pre-queries} & q &::=& \mt{SELECT} \; [\mt{DISTINCT}] \; P \; \mt{FROM} \; T,^+ \; [\mt{WHERE} \; E] \; [\mt{GROUP} \; \mt{BY} \; p,^+] \; [\mt{HAVING} \; E] \\ - &&& \mid q \; R \; q \\ + \textrm{Pre-queries} & q &::=& \mt{SELECT} \; [\mt{DISTINCT}] \; P \; \mt{FROM} \; F,^+ \; [\mt{WHERE} \; E] \; [\mt{GROUP} \; \mt{BY} \; p,^+] \; [\mt{HAVING} \; E] \\ + &&& \mid q \; R \; q \mid \{\{\{e\}\}\} \\ \textrm{Relational operators} & R &::=& \mt{UNION} \mid \mt{INTERSECT} \mid \mt{EXCEPT} \end{array}$$ @@ -1875,6 +1895,10 @@ $$\begin{array}{rrcll} \textrm{Tables} & T &::=& x & \textrm{table variable, named locally by its own capitalization} \\ &&& x \; \mt{AS} \; t & \textrm{table variable, with local name} \\ &&& \{\{e\}\} \; \mt{AS} \; t & \textrm{computed table expression, with local name} \\ + \textrm{$\mt{FROM}$ items} & F &::=& T \mid \{\{e\}\} \mid F \; J \; \mt{JOIN} \; F \; \mt{ON} \; E \\ + &&& \mid F \; \mt{CROSS} \; \mt{JOIN} \ F \\ + \textrm{Joins} & J &::=& [\mt{INNER}] \\ + &&& \mid [\mt{LEFT} \mid \mt{RIGHT} \mid \mt{FULL}] \; [\mt{OUTER}] \\ \textrm{SQL expressions} & E &::=& p & \textrm{column references} \\ &&& X & \textrm{named expression references} \\ &&& \{\{e\}\} & \textrm{injected native Ur expressions} \\ @@ -1897,7 +1921,7 @@ $$\begin{array}{rrcll} \textrm{SQL integer} & N &::=& n \mid \{e\} \\ \end{array}$$ -Additionally, an SQL expression may be inserted into normal Ur code with the syntax $(\mt{SQL} \; E)$ or $(\mt{WHERE} \; E)$. +Additionally, an SQL expression may be inserted into normal Ur code with the syntax $(\mt{SQL} \; E)$ or $(\mt{WHERE} \; E)$. Similar shorthands exist for other nonterminals, with the prefix $\mt{FROM}$ for $\mt{FROM}$ items and $\mt{SELECT1}$ for pre-queries. \subsubsection{DML} @@ -2005,9 +2029,15 @@ void uw_register_transactional(uw_context, void *data, uw_callback commit, \end{verbatim} All side effects in Ur/Web programs need to be compatible with transactions, such that any set of actions can be undone at any time. Thus, you should not perform actions with non-local side effects directly; instead, register handlers to be called when the current transaction is committed or rolled back. The arguments here give an arbitary piece of data to be passed to callbacks, a function to call on commit, a function to call on rollback, and a function to call afterward in either case to clean up any allocated resources. A rollback handler may be called after the associated commit handler has already been called, if some later part of the commit process fails. - To accommodate some stubbornly non-transactional real-world actions like sending an e-mail message, Ur/Web allows the \texttt{rollback} parameter to be \texttt{NULL}. When a transaction commits, all \texttt{commit} actions that have non-\texttt{NULL} rollback actions are tried before any \texttt{commit} actions that have \texttt{NULL} rollback actions. Thus, if a single execution uses only one non-transactional action, and if that action never fails partway through its execution while still causing an observable side effect, then Ur/Web can maintain the transactional abstraction. -\end{itemize} + Any of the callbacks may be \texttt{NULL}. To accommodate some stubbornly non-transactional real-world actions like sending an e-mail message, Ur/Web treats \texttt{NULL} \texttt{rollback} callbacks specially. When a transaction commits, all \texttt{commit} actions that have non-\texttt{NULL} rollback actions are tried before any \texttt{commit} actions that have \texttt{NULL} rollback actions. Thus, if a single execution uses only one non-transactional action, and if that action never fails partway through its execution while still causing an observable side effect, then Ur/Web can maintain the transactional abstraction. + + \item \begin{verbatim} +void *uw_get_global(uw_context, char *name); +void uw_set_global(uw_context, char *name, void *data, uw_callback free); + \end{verbatim} + Different FFI-based extensions may want to associate their own pieces of data with contexts. The global interface provides a way of doing that, where each extension must come up with its own unique key. The \texttt{free} argument to \texttt{uw\_set\_global()} explains how to deallocate the saved data. +\end{itemize} \subsection{Writing JavaScript FFI Code} diff --git a/include/urweb.h b/include/urweb.h index 31b0da5d..69e94c9b 100644 --- a/include/urweb.h +++ b/include/urweb.h @@ -232,7 +232,7 @@ extern char *uw_sqlsuffixBlob; extern char *uw_sqlfmtUint4; void *uw_get_global(uw_context, char *name); -void uw_set_global(uw_context, char *name, void *data, void (*free)(void*)); +void uw_set_global(uw_context, char *name, void *data, uw_callback free); uw_Basis_bool uw_Basis_isalnum(uw_context, uw_Basis_char); uw_Basis_bool uw_Basis_isalpha(uw_context, uw_Basis_char); diff --git a/lib/ur/basis.urs b/lib/ur/basis.urs index 87454ea5..02ac0126 100644 --- a/lib/ur/basis.urs +++ b/lib/ur/basis.urs @@ -585,9 +585,9 @@ type url val show_url : show url val bless : string -> url val checkUrl : string -> option url +val currentUrl : transaction url val url : transaction page -> url val redirect : t ::: Type -> url -> transaction t -val currentUrl : transaction url val dyn : ctx ::: {Unit} -> use ::: {Type} -> bind ::: {Type} -> [ctx ~ body] => unit -> tag [Signal = signal (xml (body ++ ctx) use bind)] (body ++ ctx) [] use bind -- cgit v1.2.3 From 0c607fbf24eb6e55f99f80ee725c02f94cc10d1a Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 24 Dec 2009 10:02:48 -0500 Subject: -tc flag --- doc/manual.tex | 5 +++++ src/main.mlton.sml | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 11d1b2d9..25c5d9b6 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -168,6 +168,11 @@ To time how long the different compiler phases run, without generating an execut urweb -timing P \end{verbatim} +To stop the compilation process after type-checking, run +\begin{verbatim} +urweb -tc P +\end{verbatim} + Some other command-line parameters are accepted: \begin{itemize} \item \texttt{-db }: Set database connection information, using the format expected by Postgres's \texttt{PQconnectdb()}, which is \texttt{name1=value1 ... nameN=valueN}. The same format is also parsed and used to discover connection parameters for MySQL and SQLite. The only significant settings for MySQL are \texttt{host}, \texttt{hostaddr}, \texttt{port}, \texttt{dbname}, \texttt{user}, and \texttt{password}. The only significant setting for SQLite is \texttt{dbname}, which is interpreted as the filesystem path to the database. Additionally, when using SQLite, a database string may be just a file path. diff --git a/src/main.mlton.sml b/src/main.mlton.sml index ff54a7fa..36d0ce98 100644 --- a/src/main.mlton.sml +++ b/src/main.mlton.sml @@ -26,6 +26,7 @@ *) val timing = ref false +val tc = ref false val sources = ref ([] : string list) val demo = ref (NONE : (string * bool) option) @@ -53,6 +54,9 @@ fun doArgs args = | "-timing" :: rest => (timing := true; doArgs rest) + | "-tc" :: rest => + (tc := true; + doArgs rest) | "-output" :: s :: rest => (Settings.setExe (SOME s); doArgs rest) @@ -78,7 +82,13 @@ val () = SOME (prefix, guided) => Demo.make {prefix = prefix, dirname = job, guided = guided} | NONE => - if !timing then + if !tc then + (Compiler.check Compiler.toElaborate job; + if ErrorMsg.anyErrors () then + OS.Process.exit OS.Process.failure + else + ()) + else if !timing then Compiler.time Compiler.toCjrize job else Compiler.compiler job -- cgit v1.2.3 From 4acc576d52308431a4d0311e8b37984ab3d0b0bc Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 24 Dec 2009 15:49:52 -0500 Subject: Convenience libifying; allow more NULLs with globals --- doc/manual.tex | 2 +- src/c/urweb.c | 9 ++++----- src/compiler.sig | 2 ++ src/compiler.sml | 24 +++++++++++++++++++++--- 4 files changed, 28 insertions(+), 9 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 25c5d9b6..5c197116 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -142,7 +142,7 @@ Here is the complete list of directive forms. ``FFI'' stands for ``foreign func \item \texttt{ffi FILENAME} reads the file \texttt{FILENAME.urs} to determine the interface to a new FFI module. The name of the module is calculated from \texttt{FILENAME} in the same way as for normal source files. See the files \texttt{include/urweb.h} and \texttt{src/c/urweb.c} for examples of C headers and implementations for FFI modules. In general, every type or value \texttt{Module.ident} becomes \texttt{uw\_Module\_ident} in C. \item \texttt{header FILENAME} adds \texttt{FILENAME} to the list of files to be \texttt{\#include}d in C sources. This is most useful for interfacing with new FFI modules. \item \texttt{jsFunc Module.ident=name} gives the JavaScript name of an FFI value. -\item \texttt{library FILENAME} parses \texttt{FILENAME.urp} and merges its contents with the rest of the current file's contents. +\item \texttt{library FILENAME} parses \texttt{FILENAME.urp} and merges its contents with the rest of the current file's contents. If \texttt{FILENAME.urp} doesn't exist, the compiler also tries \texttt{FILENAME/lib.urp}. \item \texttt{link FILENAME} adds \texttt{FILENAME} to the list of files to be passed to the GCC linker at the end of compilation. This is most useful for importing extra libraries needed by new FFI modules. \item \texttt{path NAME=VALUE} creates a mapping from \texttt{NAME} to \texttt{VALUE}. This mapping may be used at the beginnings of filesystem paths given to various other configuration directives. A path like \texttt{\$NAME/rest} is expanded to \texttt{VALUE/rest}. There is an initial mapping from the empty name (for paths like \texttt{\$/list}) to the directory where the Ur/Web standard library is installed. If you accept the default \texttt{configure} options, this directory is \texttt{/usr/local/lib/urweb/ur}. \item \texttt{prefix PREFIX} sets the prefix included before every URI within the generated application. The default is \texttt{/}. diff --git a/src/c/urweb.c b/src/c/urweb.c index 1f8271d5..4a0306e0 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -468,7 +468,8 @@ void uw_free(uw_context ctx) { buf_free(&ctx->deltas[i].msgs); for (i = 0; i < ctx->n_globals; ++i) - ctx->globals[i].free(ctx->globals[i].data); + if (ctx->globals[i].free) + ctx->globals[i].free(ctx->globals[i].data); free(ctx); } @@ -3177,17 +3178,15 @@ void *uw_get_global(uw_context ctx, char *name) { void uw_set_global(uw_context ctx, char *name, void *data, void (*free)(void*)) { int i; - if (data == NULL) uw_error(ctx, FATAL, "NULL data value for global '%s'", name); - for (i = 0; i < ctx->n_globals; ++i) if (!strcmp(name, ctx->globals[i].name)) { - if (ctx->globals[i].data) + if (ctx->globals[i].free) ctx->globals[i].free(ctx->globals[i].data); ctx->globals[i].data = data; ctx->globals[i].free = free; return; } - + ++ctx->n_globals; ctx->globals = realloc(ctx->globals, ctx->n_globals * sizeof(global)); ctx->globals[ctx->n_globals-1].name = name; diff --git a/src/compiler.sig b/src/compiler.sig index e34b771e..fbc3011e 100644 --- a/src/compiler.sig +++ b/src/compiler.sig @@ -156,4 +156,6 @@ signature COMPILER = sig val debug : bool ref + val addPath : string * string -> unit + end diff --git a/src/compiler.sml b/src/compiler.sml index fc764205..b793ab60 100644 --- a/src/compiler.sml +++ b/src/compiler.sml @@ -270,9 +270,13 @@ structure M = BinaryMapFn(struct val compare = String.compare end) +val pathmap = ref (M.insert (M.empty, "", Config.libUr)) + +fun addPath (k, v) = pathmap := M.insert (!pathmap, k, v) + fun parseUrp' accLibs fname = let - val pathmap = ref (M.insert (M.empty, "", Config.libUr)) + val pathmap = ref (!pathmap) val bigLibs = ref [] fun pu filename = @@ -304,6 +308,20 @@ fun parseUrp' accLibs fname = handle OS.Path.Path => fname end + fun libify path = + (if Posix.FileSys.access (path ^ ".urp", []) then + path + else + path ^ "/lib") + handle SysErr => path + + fun libify' path = + (if Posix.FileSys.access (relify path ^ ".urp", []) then + path + else + path ^ "/lib") + handle SysErr => path + val absDir = OS.Path.mkAbsolute {path = dir, relativeTo = OS.FileSys.getDir ()} fun relifyA fname = @@ -559,9 +577,9 @@ fun parseUrp' accLibs fname = end | _ => ErrorMsg.error "Bad 'deny' syntax") | "library" => if accLibs then - libs := pu (relify arg) :: !libs + libs := pu (libify (relify arg)) :: !libs else - bigLibs := arg :: !bigLibs + bigLibs := libify' arg :: !bigLibs | "path" => (case String.fields (fn ch => ch = #"=") arg of [n, v] => pathmap := M.insert (!pathmap, n, v) -- cgit v1.2.3 From c5a46fa63373c129e9595373d4c126030266d957 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 27 Dec 2009 13:18:32 -0500 Subject: Dynamic linking of the runtime system --- .hgignore | 2 + CHANGELOG | 1 + Makefile.in | 28 +- configure | 2013 ++++++++++++++++++++++++++++------------------------ doc/manual.tex | 2 + src/cgi.sml | 3 +- src/compiler.sml | 12 +- src/fastcgi.sml | 3 +- src/http.sml | 3 +- src/main.mlton.sml | 3 + src/settings.sig | 10 +- src/settings.sml | 10 +- 12 files changed, 1148 insertions(+), 942 deletions(-) (limited to 'doc') diff --git a/.hgignore b/.hgignore index ab9d0f88..f56e5932 100644 --- a/.hgignore +++ b/.hgignore @@ -12,6 +12,8 @@ src/urweb.mlb *.lex.* *.grm.* *.o +*.do +*.so.* src/config.sml diff --git a/CHANGELOG b/CHANGELOG index 16172985..3f1f5c3f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ Next - Typing of SQL queries no longer exposes which tables were used in joins but had none of their fields projected - Tasks +- Dynamic linking of the runtime system - Optimization improvements ======== diff --git a/Makefile.in b/Makefile.in index ac72bb04..5016abb3 100644 --- a/Makefile.in +++ b/Makefile.in @@ -5,7 +5,10 @@ SITELISP := @SITELISP@ LIB_UR := $(LIB)/ur LIB_C := $(LIB)/c -LIB_JS := $(LIB)/js +LIB_JS := $(LIB)/js + +LD_MAJOR := 1 +LD_MINOR := 0 all: smlnj mlton c @@ -14,18 +17,29 @@ all: smlnj mlton c smlnj: src/urweb.cm mlton: bin/urweb -OBJS := urweb request queue http cgi fastcgi memmem -c: $(OBJS:%=lib/c/%.o) +OBJS := memmem urweb request queue http cgi fastcgi +SOS := urweb urweb_http urweb_cgi urweb_fastcgi +c: $(OBJS:%=lib/c/%.o) $(SOS:%=lib/c/lib%.so.$(LD_MAJOR).$(LD_MINOR)) clean: rm -f src/*.mlton.grm.* src/*.mlton.lex.* \ src/urweb.cm src/urweb.mlb \ - lib/c/*.o + lib/c/*.o lib/c/*.so.* rm -rf .cm src/.cm +lib/c/%.do: src/c/%.c include/*.h + gcc -fPIC -Wimplicit -O3 -I include -c $< -o $@ $(CFLAGS) + lib/c/%.o: src/c/%.c include/*.h gcc -Wimplicit -O3 -I include -c $< -o $@ $(CFLAGS) +URWEB_OS := memmem urweb queue request +lib/c/liburweb.so.$(LD_MAJOR).$(LD_MINOR): $(URWEB_OS:%=lib/c/%.do) + gcc -shared -Wl,-soname,liburweb.so.$(LD_MAJOR) -o $@ $^ + +lib/c/liburweb_%.so.$(LD_MAJOR).$(LD_MINOR): lib/c/%.do + gcc -shared -Wl,-soname,liburweb_$*.so.$(LD_MAJOR) -o $@ $^ + src/urweb.cm: src/prefix.cm src/sources cat src/prefix.cm src/sources \ >src/urweb.cm @@ -70,12 +84,18 @@ install: cp lib/ur/*.ur $(LIB_UR)/ mkdir -p $(LIB_C) cp lib/c/*.o $(LIB_C)/ + cp lib/c/*.so.$(LD_MAJOR).$(LD_MINOR) $(LIB_C)/ mkdir -p $(LIB_JS) cp lib/js/*.js $(LIB_JS)/ mkdir -p $(INCLUDE) cp include/*.h $(INCLUDE)/ mkdir -p $(SITELISP) cp src/elisp/*.el $(SITELISP)/ + ldconfig $(LIB_C) + ln -sf $(LIB_C)/liburweb.so.$(LD_MAJOR) $(LIB_C)/liburweb.so + ln -sf $(LIB_C)/liburweb_http.so.$(LD_MAJOR) $(LIB_C)/liburweb_http.so + ln -sf $(LIB_C)/liburweb_cgi.so.$(LD_MAJOR) $(LIB_C)/liburweb_cgi.so + ln -sf $(LIB_C)/liburweb_fastcgi.so.$(LD_MAJOR) $(LIB_C)/liburweb_fastcgi.so package: hg archive -t tgz -X tests /tmp/urweb.tgz diff --git a/configure b/configure index e34c6b57..fdb97c05 100755 --- a/configure +++ b/configure @@ -1,60 +1,83 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.61. +# Generated by GNU Autoconf 2.65. +# # # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, -# 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. +# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# +# # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. -## --------------------- ## -## M4sh Initialization. ## -## --------------------- ## +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: - # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else - case `(set -o) 2>/dev/null` in - *posix*) set -o posix ;; + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; esac - fi - - -# PATH needs CR -# Avoid depending upon Character Ranges. -as_cr_letters='abcdefghijklmnopqrstuvwxyz' -as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' -as_cr_Letters=$as_cr_letters$as_cr_LETTERS -as_cr_digits='0123456789' -as_cr_alnum=$as_cr_Letters$as_cr_digits - -# The user is always right. -if test "${PATH_SEPARATOR+set}" != set; then - echo "#! /bin/sh" >conf$$.sh - echo "exit 0" >>conf$$.sh - chmod +x conf$$.sh - if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then - PATH_SEPARATOR=';' +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' else - PATH_SEPARATOR=: + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' fi - rm -f conf$$.sh + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' fi -# Support unset when possible. -if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then - as_unset=unset -else - as_unset=false +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } fi @@ -63,20 +86,18 @@ fi # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) -as_nl=' -' IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. -case $0 in +case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. - test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break -done + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done IFS=$as_save_IFS ;; @@ -87,354 +108,320 @@ if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 - { (exit 1); exit 1; } + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 fi -# Work around bugs in pre-3.0 UWIN ksh. -for as_var in ENV MAIL MAILPATH -do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. -for as_var in \ - LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \ - LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \ - LC_TELEPHONE LC_TIME -do - if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then - eval $as_var=C; export $as_var - else - ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var - fi -done - -# Required to use basename. -if expr a : '\(a\)' >/dev/null 2>&1 && - test "X`expr 00001 : '.*\(...\)'`" = X001; then - as_expr=expr -else - as_expr=false -fi - -if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then - as_basename=basename -else - as_basename=false -fi - - -# Name of the executable. -as_me=`$as_basename -- "$0" || -$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ - X"$0" : 'X\(//\)$' \| \ - X"$0" : 'X\(/\)' \| . 2>/dev/null || -echo X/"$0" | - sed '/^.*\/\([^/][^/]*\)\/*$/{ - s//\1/ - q - } - /^X\/\(\/\/\)$/{ - s//\1/ - q - } - /^X\/\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE # CDPATH. -$as_unset CDPATH - +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH if test "x$CONFIG_SHELL" = x; then - if (eval ":") 2>/dev/null; then - as_have_required=yes + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST else - as_have_required=no + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac fi - - if test $as_have_required = yes && (eval ": -(as_func_return () { - (exit \$1) -} -as_func_success () { - as_func_return 0 -} -as_func_failure () { - as_func_return 1 -} -as_func_ret_success () { - return 0 -} -as_func_ret_failure () { - return 1 -} +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } exitcode=0 -if as_func_success; then - : -else - exitcode=1 - echo as_func_success failed. -fi +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : -if as_func_failure; then - exitcode=1 - echo as_func_failure succeeded. -fi - -if as_func_ret_success; then - : else - exitcode=1 - echo as_func_ret_success failed. -fi - -if as_func_ret_failure; then - exitcode=1 - echo as_func_ret_failure succeeded. -fi - -if ( set x; as_func_ret_success y && test x = \"\$1\" ); then - : + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes else - exitcode=1 - echo positional parameters were not saved. + as_have_required=no fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : -test \$exitcode = 0) || { (exit 1); exit 1; } - -( - as_lineno_1=\$LINENO - as_lineno_2=\$LINENO - test \"x\$as_lineno_1\" != \"x\$as_lineno_2\" && - test \"x\`expr \$as_lineno_1 + 1\`\" = \"x\$as_lineno_2\") || { (exit 1); exit 1; } -") 2> /dev/null; then - : else - as_candidate_shells= - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. - case $as_dir in + as_found=: + case $as_dir in #( /*) for as_base in sh bash ksh sh5; do - as_candidate_shells="$as_candidate_shells $as_dir/$as_base" + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi done;; esac + as_found=false done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } IFS=$as_save_IFS - for as_shell in $as_candidate_shells $SHELL; do - # Try only shells that exist, to save several forks. - if { test -f "$as_shell" || test -f "$as_shell.exe"; } && - { ("$as_shell") 2> /dev/null <<\_ASEOF -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then - emulate sh - NULLCMD=: - # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. - alias -g '${1+"$@"}'='"$@"' - setopt NO_GLOB_SUBST -else - case `(set -o) 2>/dev/null` in - *posix*) set -o posix ;; -esac - + if test "x$CONFIG_SHELL" != x; then : + # We cannot yet assume a decent shell, so we have to provide a + # neutralization value for shells without unset; and this also + # works around shells that cannot unset nonexistent variables. + BASH_ENV=/dev/null + ENV=/dev/null + (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV + export CONFIG_SHELL + exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"} fi - -: -_ASEOF -}; then - CONFIG_SHELL=$as_shell - as_have_required=yes - if { "$as_shell" 2> /dev/null <<\_ASEOF -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then - emulate sh - NULLCMD=: - # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. - alias -g '${1+"$@"}'='"$@"' - setopt NO_GLOB_SUBST -else - case `(set -o) 2>/dev/null` in - *posix*) set -o posix ;; -esac - + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS - -: -(as_func_return () { - (exit $1) -} -as_func_success () { - as_func_return 0 -} -as_func_failure () { - as_func_return 1 -} -as_func_ret_success () { - return 0 -} -as_func_ret_failure () { - return 1 +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} } +as_unset=as_fn_unset -exitcode=0 -if as_func_success; then - : -else - exitcode=1 - echo as_func_success failed. -fi +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status -if as_func_failure; then - exitcode=1 - echo as_func_failure succeeded. -fi +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ -if as_func_ret_success; then - : + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' else - exitcode=1 - echo as_func_ret_success failed. -fi - -if as_func_ret_failure; then - exitcode=1 - echo as_func_ret_failure succeeded. -fi - -if ( set x; as_func_ret_success y && test x = "$1" ); then - : + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' else - exitcode=1 - echo positional parameters were not saved. -fi - -test $exitcode = 0) || { (exit 1); exit 1; } - -( - as_lineno_1=$LINENO - as_lineno_2=$LINENO - test "x$as_lineno_1" != "x$as_lineno_2" && - test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2") || { (exit 1); exit 1; } - -_ASEOF -}; then - break -fi - -fi - - done - - if test "x$CONFIG_SHELL" != x; then - for as_var in BASH_ENV ENV - do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var - done - export CONFIG_SHELL - exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"} -fi - - - if test $as_have_required = no; then - echo This script requires a shell more modern than all the - echo shells that I found on your system. Please install a - echo modern shell, or manually run the script under such a - echo shell if you do have one. - { (exit 1); exit 1; } -fi - - -fi - -fi - + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith -(eval "as_func_return () { - (exit \$1) -} -as_func_success () { - as_func_return 0 -} -as_func_failure () { - as_func_return 1 -} -as_func_ret_success () { - return 0 -} -as_func_ret_failure () { - return 1 -} +# as_fn_error ERROR [LINENO LOG_FD] +# --------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with status $?, using 1 if that was 0. +as_fn_error () +{ + as_status=$?; test $as_status -eq 0 && as_status=1 + if test "$3"; then + as_lineno=${as_lineno-"$2"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $1" >&$3 + fi + $as_echo "$as_me: error: $1" >&2 + as_fn_exit $as_status +} # as_fn_error -exitcode=0 -if as_func_success; then - : +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr else - exitcode=1 - echo as_func_success failed. -fi - -if as_func_failure; then - exitcode=1 - echo as_func_failure succeeded. + as_expr=false fi -if as_func_ret_success; then - : +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename else - exitcode=1 - echo as_func_ret_success failed. -fi - -if as_func_ret_failure; then - exitcode=1 - echo as_func_ret_failure succeeded. + as_basename=false fi -if ( set x; as_func_ret_success y && test x = \"\$1\" ); then - : +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname else - exitcode=1 - echo positional parameters were not saved. + as_dirname=false fi -test \$exitcode = 0") || { - echo No shell found that supports shell functions. - echo Please tell autoconf@gnu.org about your system, - echo including any error possibly output before this - echo message -} - +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits - as_lineno_1=$LINENO - as_lineno_2=$LINENO - test "x$as_lineno_1" != "x$as_lineno_2" && - test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2" || { - # Create $as_me.lineno as a copy of $as_myself, but with $LINENO - # uniformly replaced by the line number. The first 'sed' inserts a - # line-number line after each line using $LINENO; the second 'sed' - # does the real work. The second script uses 'N' to pair each - # line-number line with the line containing $LINENO, and appends - # trailing '-' during substitution so that $LINENO is not a special - # case at line end. - # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the - # scripts with optimization help from Paolo Bonzini. Blame Lee - # E. McMahon (1931-1989) for sed's syntax. :-) + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= @@ -451,8 +438,7 @@ test \$exitcode = 0") || { s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || - { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2 - { (exit 1); exit 1; }; } + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the @@ -462,49 +448,40 @@ test \$exitcode = 0") || { exit } - -if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then - as_dirname=dirname -else - as_dirname=false -fi - ECHO_C= ECHO_N= ECHO_T= -case `echo -n x` in +case `echo -n x` in #((((( -n*) - case `echo 'x\c'` in + case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. - *) ECHO_C='\c';; + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac -if expr a : '\(a\)' >/dev/null 2>&1 && - test "X`expr 00001 : '.*\(...\)'`" = X001; then - as_expr=expr -else - as_expr=false -fi - rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir - mkdir conf$$.dir -fi -echo >conf$$.file -if ln -s conf$$.file conf$$ 2>/dev/null; then - as_ln_s='ln -s' - # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -p'. - ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -p'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -p' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else as_ln_s='cp -p' -elif ln conf$$.file conf$$ 2>/dev/null; then - as_ln_s=ln + fi else as_ln_s='cp -p' fi @@ -512,7 +489,7 @@ rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then - as_mkdir_p=: + as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false @@ -529,12 +506,12 @@ else as_test_x=' eval sh -c '\'' if test -d "$1"; then - test -d "$1/."; + test -d "$1/."; else - case $1 in - -*)set "./$1";; + case $1 in #( + -*)set "./$1";; esac; - case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in + case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( ???[sx]*):;;*)false;;esac;fi '\'' sh ' @@ -548,8 +525,8 @@ as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" - -exec 7<&0 &1 +test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, Linux) returns a bogus exit status, @@ -567,7 +544,6 @@ cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= -SHELL=${CONFIG_SHELL-/bin/sh} # Identity of this package. PACKAGE_NAME= @@ -575,54 +551,59 @@ PACKAGE_TARNAME= PACKAGE_VERSION= PACKAGE_STRING= PACKAGE_BUGREPORT= +PACKAGE_URL= ac_unique_file="THIS_IS_URWEB" -ac_subst_vars='SHELL -PATH_SEPARATOR -PACKAGE_NAME -PACKAGE_TARNAME -PACKAGE_VERSION -PACKAGE_STRING -PACKAGE_BUGREPORT -exec_prefix -prefix -program_transform_name -bindir -sbindir -libexecdir -datarootdir -datadir -sysconfdir -sharedstatedir -localstatedir -includedir -oldincludedir -docdir -infodir -htmldir -dvidir -pdfdir -psdir -libdir -localedir -mandir -DEFS -ECHO_C -ECHO_N -ECHO_T -LIBS -build_alias -host_alias -target_alias -BIN -LIB -INCLUDE -SITELISP -GCCARGS -do_not_edit +ac_subst_vars='LTLIBOBJS LIBOBJS -LTLIBOBJS' +do_not_edit +GCCARGS +SITELISP +INCLUDE +LIB +BIN +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' ac_subst_files='' +ac_user_opts=' +enable_option_checking +' ac_precious_vars='build_alias host_alias target_alias' @@ -631,6 +612,8 @@ target_alias' # Initialize some variables set by options. ac_init_help= ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null @@ -729,13 +712,20 @@ do datarootdir=$ac_optarg ;; -disable-* | --disable-*) - ac_feature=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. - expr "x$ac_feature" : ".*[^-._$as_cr_alnum]" >/dev/null && - { echo "$as_me: error: invalid feature name: $ac_feature" >&2 - { (exit 1); exit 1; }; } - ac_feature=`echo $ac_feature | sed 's/[-.]/_/g'` - eval enable_$ac_feature=no ;; + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; @@ -748,13 +738,20 @@ do dvidir=$ac_optarg ;; -enable-* | --enable-*) - ac_feature=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. - expr "x$ac_feature" : ".*[^-._$as_cr_alnum]" >/dev/null && - { echo "$as_me: error: invalid feature name: $ac_feature" >&2 - { (exit 1); exit 1; }; } - ac_feature=`echo $ac_feature | sed 's/[-.]/_/g'` - eval enable_$ac_feature=\$ac_optarg ;; + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ @@ -945,22 +942,36 @@ do ac_init_version=: ;; -with-* | --with-*) - ac_package=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. - expr "x$ac_package" : ".*[^-._$as_cr_alnum]" >/dev/null && - { echo "$as_me: error: invalid package name: $ac_package" >&2 - { (exit 1); exit 1; }; } - ac_package=`echo $ac_package | sed 's/[-.]/_/g'` - eval with_$ac_package=\$ac_optarg ;; + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) - ac_package=`expr "x$ac_option" : 'x-*without-\(.*\)'` + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. - expr "x$ac_package" : ".*[^-._$as_cr_alnum]" >/dev/null && - { echo "$as_me: error: invalid package name: $ac_package" >&2 - { (exit 1); exit 1; }; } - ac_package=`echo $ac_package | sed 's/[-.]/_/g'` - eval with_$ac_package=no ;; + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. @@ -980,25 +991,25 @@ do | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; - -*) { echo "$as_me: error: unrecognized option: $ac_option -Try \`$0 --help' for more information." >&2 - { (exit 1); exit 1; }; } + -*) as_fn_error "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information." ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. - expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null && - { echo "$as_me: error: invalid variable name: $ac_envvar" >&2 - { (exit 1); exit 1; }; } + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error "invalid variable name: \`$ac_envvar'" ;; + esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. - echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && - echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option} ;; @@ -1007,23 +1018,36 @@ done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` - { echo "$as_me: error: missing argument to $ac_option" >&2 - { (exit 1); exit 1; }; } + as_fn_error "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac fi -# Be sure to have absolute directory names. +# Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir do eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac - { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2 - { (exit 1); exit 1; }; } + as_fn_error "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' @@ -1037,7 +1061,7 @@ target=$target_alias if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe - echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host. + $as_echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host. If a cross compiler is detected then cross compile mode will be used." >&2 elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes @@ -1053,23 +1077,21 @@ test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || - { echo "$as_me: error: Working directory cannot be determined" >&2 - { (exit 1); exit 1; }; } + as_fn_error "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || - { echo "$as_me: error: pwd does not report name of working directory" >&2 - { (exit 1); exit 1; }; } + as_fn_error "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. - ac_confdir=`$as_dirname -- "$0" || -$as_expr X"$0" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$0" : 'X\(//\)[^/]' \| \ - X"$0" : 'X\(//\)$' \| \ - X"$0" : 'X\(/\)' \| . 2>/dev/null || -echo X"$0" | + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -1096,13 +1118,11 @@ else fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." - { echo "$as_me: error: cannot find sources ($ac_unique_file) in $srcdir" >&2 - { (exit 1); exit 1; }; } + as_fn_error "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( - cd "$srcdir" && test -r "./$ac_unique_file" || { echo "$as_me: error: $ac_msg" >&2 - { (exit 1); exit 1; }; } + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then @@ -1150,9 +1170,9 @@ Configuration: Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX - [$ac_default_prefix] + [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX - [PREFIX] + [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify @@ -1162,25 +1182,25 @@ for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: - --bindir=DIR user executables [EPREFIX/bin] - --sbindir=DIR system admin executables [EPREFIX/sbin] - --libexecdir=DIR program executables [EPREFIX/libexec] - --sysconfdir=DIR read-only single-machine data [PREFIX/etc] - --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] - --localstatedir=DIR modifiable single-machine data [PREFIX/var] - --libdir=DIR object code libraries [EPREFIX/lib] - --includedir=DIR C header files [PREFIX/include] - --oldincludedir=DIR C header files for non-gcc [/usr/include] - --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] - --datadir=DIR read-only architecture-independent data [DATAROOTDIR] - --infodir=DIR info documentation [DATAROOTDIR/info] - --localedir=DIR locale-dependent data [DATAROOTDIR/locale] - --mandir=DIR man documentation [DATAROOTDIR/man] - --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] - --htmldir=DIR html documentation [DOCDIR] - --dvidir=DIR dvi documentation [DOCDIR] - --pdfdir=DIR pdf documentation [DOCDIR] - --psdir=DIR ps documentation [DOCDIR] + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF @@ -1191,6 +1211,7 @@ if test -n "$ac_init_help"; then cat <<\_ACEOF +Report bugs to the package provider. _ACEOF ac_status=$? fi @@ -1198,15 +1219,17 @@ fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue - test -d "$ac_dir" || continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'` + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,/..,g;s,/,,'` + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -1242,7 +1265,7 @@ ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix echo && $SHELL "$ac_srcdir/configure" --help=recursive else - echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done @@ -1252,21 +1275,24 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF configure -generated by GNU Autoconf 2.61 +generated by GNU Autoconf 2.65 -Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, -2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. +Copyright (C) 2009 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by $as_me, which was -generated by GNU Autoconf 2.61. Invocation command line was +generated by GNU Autoconf 2.65. Invocation command line was $ $0 $@ @@ -1302,8 +1328,8 @@ for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. - echo "PATH: $as_dir" -done + $as_echo "PATH: $as_dir" + done IFS=$as_save_IFS } >&5 @@ -1337,12 +1363,12 @@ do | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) - ac_arg=`echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in - 1) ac_configure_args0="$ac_configure_args0 '$ac_arg'" ;; + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) - ac_configure_args1="$ac_configure_args1 '$ac_arg'" + as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else @@ -1358,13 +1384,13 @@ do -* ) ac_must_keep_next=true ;; esac fi - ac_configure_args="$ac_configure_args '$ac_arg'" + as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done -$as_unset ac_configure_args0 || test "${ac_configure_args0+set}" != set || { ac_configure_args0=; export ac_configure_args0; } -$as_unset ac_configure_args1 || test "${ac_configure_args1+set}" != set || { ac_configure_args1=; export ac_configure_args1; } +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there @@ -1389,12 +1415,13 @@ _ASBOX case $ac_val in #( *${as_nl}*) case $ac_var in #( - *_cv_*) { echo "$as_me:$LINENO: WARNING: Cache variable $ac_var contains a newline." >&5 -echo "$as_me: WARNING: Cache variable $ac_var contains a newline." >&2;} ;; + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( - *) $as_unset $ac_var ;; + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done @@ -1423,9 +1450,9 @@ _ASBOX do eval ac_val=\$$ac_var case $ac_val in - *\'\''*) ac_val=`echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac - echo "$ac_var='\''$ac_val'\''" + $as_echo "$ac_var='\''$ac_val'\''" done | sort echo @@ -1440,9 +1467,9 @@ _ASBOX do eval ac_val=\$$ac_var case $ac_val in - *\'\''*) ac_val=`echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac - echo "$ac_var='\''$ac_val'\''" + $as_echo "$ac_var='\''$ac_val'\''" done | sort echo fi @@ -1458,83 +1485,88 @@ _ASBOX echo fi test "$ac_signal" != 0 && - echo "$as_me: caught signal $ac_signal" - echo "$as_me: exit $exit_status" + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do - trap 'ac_signal='$ac_signal'; { (exit 1); exit 1; }' $ac_signal + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h +$as_echo "/* confdefs.h */" > confdefs.h + # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF - cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF - cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF - cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF - cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + # Let the site file select an alternate cache file if it wants to. -# Prefer explicitly selected file to automatically selected ones. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE if test -n "$CONFIG_SITE"; then - set x "$CONFIG_SITE" + ac_site_file1=$CONFIG_SITE elif test "x$prefix" != xNONE; then - set x "$prefix/share/config.site" "$prefix/etc/config.site" + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site else - set x "$ac_default_prefix/share/config.site" \ - "$ac_default_prefix/etc/config.site" + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site fi -shift -for ac_site_file +for ac_site_file in "$ac_site_file1" "$ac_site_file2" do - if test -r "$ac_site_file"; then - { echo "$as_me:$LINENO: loading site script $ac_site_file" >&5 -echo "$as_me: loading site script $ac_site_file" >&6;} + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" fi done if test -r "$cache_file"; then - # Some versions of bash will fail to source /dev/null (special - # files actually), so we avoid doing that. - if test -f "$cache_file"; then - { echo "$as_me:$LINENO: loading cache $cache_file" >&5 -echo "$as_me: loading cache $cache_file" >&6;} + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else - { echo "$as_me:$LINENO: creating cache $cache_file" >&5 -echo "$as_me: creating cache $cache_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi @@ -1548,60 +1580,56 @@ for ac_var in $ac_precious_vars; do eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) - { echo "$as_me:$LINENO: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 -echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) - { echo "$as_me:$LINENO: error: \`$ac_var' was not set in the previous run" >&5 -echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then - { echo "$as_me:$LINENO: error: \`$ac_var' has changed since the previous run:" >&5 -echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} - { echo "$as_me:$LINENO: former value: $ac_old_val" >&5 -echo "$as_me: former value: $ac_old_val" >&2;} - { echo "$as_me:$LINENO: current value: $ac_new_val" >&5 -echo "$as_me: current value: $ac_new_val" >&2;} - ac_cache_corrupted=: + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in - *\'*) ac_arg=$ac_var=`echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. - *) ac_configure_args="$ac_configure_args '$ac_arg'" ;; + *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then - { echo "$as_me:$LINENO: error: changes in the environment can compromise the build" >&5 -echo "$as_me: error: changes in the environment can compromise the build" >&2;} - { { echo "$as_me:$LINENO: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&5 -echo "$as_me: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&2;} - { (exit 1); exit 1; }; } + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi - - - - - - - - - - - - - - - - +## -------------------- ## +## Main body of script. ## +## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' @@ -1616,14 +1644,12 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu # that's on manju, so I assume it's ok -{ echo "$as_me:$LINENO: Configuring Ur/Web" >&5 -echo "$as_me: Configuring Ur/Web" >&6;} +{ $as_echo "$as_me:${as_lineno-$LINENO}: Configuring Ur/Web" >&5 +$as_echo "$as_me: Configuring Ur/Web" >&6;} # make sure I haven't forgotten to run autoconf if test configure -ot configure.ac; then - { { echo "$as_me:$LINENO: error: configure is older than configure.in; you forgot to run autoconf" >&5 -echo "$as_me: error: configure is older than configure.in; you forgot to run autoconf" >&2;} - { (exit 1); exit 1; }; } + as_fn_error "configure is older than configure.in; you forgot to run autoconf" "$LINENO" 5 fi # ---------------- generic functions ----------------- @@ -1724,12 +1750,13 @@ _ACEOF case $ac_val in #( *${as_nl}*) case $ac_var in #( - *_cv_*) { echo "$as_me:$LINENO: WARNING: Cache variable $ac_var contains a newline." >&5 -echo "$as_me: WARNING: Cache variable $ac_var contains a newline." >&2;} ;; + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( - *) $as_unset $ac_var ;; + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done @@ -1737,8 +1764,8 @@ echo "$as_me: WARNING: Cache variable $ac_var contains a newline." >&2;} ;; (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) - # `set' does not quote correctly, so add quotes (double-quote - # substitution turns \\\\ into \\, and sed turns \\ into \). + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" @@ -1761,12 +1788,12 @@ echo "$as_me: WARNING: Cache variable $ac_var contains a newline." >&2;} ;; if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then test "x$cache_file" != "x/dev/null" && - { echo "$as_me:$LINENO: updating cache $cache_file" >&5 -echo "$as_me: updating cache $cache_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} cat confcache >$cache_file else - { echo "$as_me:$LINENO: not updating unwritable cache $cache_file" >&5 -echo "$as_me: not updating unwritable cache $cache_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache @@ -1783,6 +1810,12 @@ test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' # take arguments), then branch to the quote section. Otherwise, # look for a macro that doesn't take arguments. ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} t clear :clear s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g @@ -1812,11 +1845,11 @@ ac_ltlibobjs= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' - ac_i=`echo "$ac_i" | sed "$ac_script"` + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. - ac_libobjs="$ac_libobjs \${LIBOBJDIR}$ac_i\$U.$ac_objext" - ac_ltlibobjs="$ac_ltlibobjs \${LIBOBJDIR}$ac_i"'$U.lo' + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs @@ -1825,11 +1858,13 @@ LTLIBOBJS=$ac_ltlibobjs : ${CONFIG_STATUS=./config.status} +ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" -{ echo "$as_me:$LINENO: creating $CONFIG_STATUS" >&5 -echo "$as_me: creating $CONFIG_STATUS" >&6;} -cat >$CONFIG_STATUS <<_ACEOF +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. @@ -1839,59 +1874,79 @@ cat >$CONFIG_STATUS <<_ACEOF debug=false ac_cs_recheck=false ac_cs_silent=false -SHELL=\${CONFIG_SHELL-$SHELL} -_ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF -## --------------------- ## -## M4sh Initialization. ## -## --------------------- ## +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: - # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else - case `(set -o) 2>/dev/null` in - *posix*) set -o posix ;; + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; esac - fi - - -# PATH needs CR -# Avoid depending upon Character Ranges. -as_cr_letters='abcdefghijklmnopqrstuvwxyz' -as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' -as_cr_Letters=$as_cr_letters$as_cr_LETTERS -as_cr_digits='0123456789' -as_cr_alnum=$as_cr_Letters$as_cr_digits - -# The user is always right. -if test "${PATH_SEPARATOR+set}" != set; then - echo "#! /bin/sh" >conf$$.sh - echo "exit 0" >>conf$$.sh - chmod +x conf$$.sh - if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then - PATH_SEPARATOR=';' +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' else - PATH_SEPARATOR=: + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' fi - rm -f conf$$.sh + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' fi -# Support unset when possible. -if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then - as_unset=unset -else - as_unset=false +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } fi @@ -1900,20 +1955,18 @@ fi # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) -as_nl=' -' IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. -case $0 in +case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. - test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break -done + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done IFS=$as_save_IFS ;; @@ -1924,32 +1977,111 @@ if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 - { (exit 1); exit 1; } + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 fi -# Work around bugs in pre-3.0 UWIN ksh. -for as_var in ENV MAIL MAILPATH -do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. -for as_var in \ - LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \ - LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \ - LC_TELEPHONE LC_TIME -do - if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then - eval $as_var=C; export $as_var - else - ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error ERROR [LINENO LOG_FD] +# --------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with status $?, using 1 if that was 0. +as_fn_error () +{ + as_status=$?; test $as_status -eq 0 && as_status=1 + if test "$3"; then + as_lineno=${as_lineno-"$2"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $1" >&$3 fi -done + $as_echo "$as_me: error: $1" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + -# Required to use basename. if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr @@ -1963,13 +2095,17 @@ else as_basename=false fi +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi -# Name of the executable. as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || -echo X/"$0" | +$as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q @@ -1984,104 +2120,103 @@ echo X/"$0" | } s/.*/./; q'` -# CDPATH. -$as_unset CDPATH - - - - as_lineno_1=$LINENO - as_lineno_2=$LINENO - test "x$as_lineno_1" != "x$as_lineno_2" && - test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2" || { - - # Create $as_me.lineno as a copy of $as_myself, but with $LINENO - # uniformly replaced by the line number. The first 'sed' inserts a - # line-number line after each line using $LINENO; the second 'sed' - # does the real work. The second script uses 'N' to pair each - # line-number line with the line containing $LINENO, and appends - # trailing '-' during substitution so that $LINENO is not a special - # case at line end. - # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the - # scripts with optimization help from Paolo Bonzini. Blame Lee - # E. McMahon (1931-1989) for sed's syntax. :-) - sed -n ' - p - /[$]LINENO/= - ' <$as_myself | - sed ' - s/[$]LINENO.*/&-/ - t lineno - b - :lineno - N - :loop - s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ - t loop - s/-\n.*// - ' >$as_me.lineno && - chmod +x "$as_me.lineno" || - { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2 - { (exit 1); exit 1; }; } - - # Don't try to exec as it changes $[0], causing all sort of problems - # (the dirname of $[0] is not the place where we might find the - # original and so on. Autoconf is especially sensitive to this). - . "./$as_me.lineno" - # Exit status is that of the last command. - exit -} - - -if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then - as_dirname=dirname -else - as_dirname=false -fi +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits ECHO_C= ECHO_N= ECHO_T= -case `echo -n x` in +case `echo -n x` in #((((( -n*) - case `echo 'x\c'` in + case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. - *) ECHO_C='\c';; + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac -if expr a : '\(a\)' >/dev/null 2>&1 && - test "X`expr 00001 : '.*\(...\)'`" = X001; then - as_expr=expr -else - as_expr=false -fi - rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir - mkdir conf$$.dir -fi -echo >conf$$.file -if ln -s conf$$.file conf$$ 2>/dev/null; then - as_ln_s='ln -s' - # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -p'. - ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -p'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -p' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else as_ln_s='cp -p' -elif ln conf$$.file conf$$ 2>/dev/null; then - as_ln_s=ln + fi else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error "cannot create directory $as_dir" + + +} # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then - as_mkdir_p=: + as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false @@ -2098,12 +2233,12 @@ else as_test_x=' eval sh -c '\'' if test -d "$1"; then - test -d "$1/."; + test -d "$1/."; else - case $1 in - -*)set "./$1";; + case $1 in #( + -*)set "./$1";; esac; - case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in + case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( ???[sx]*):;;*)false;;esac;fi '\'' sh ' @@ -2118,13 +2253,19 @@ as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 -# Save the log message, to keep $[0] and so on meaningful, and to +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by $as_me, which was -generated by GNU Autoconf 2.61. Invocation command line was +generated by GNU Autoconf 2.65. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS @@ -2137,50 +2278,60 @@ on `(hostname || uname -n) 2>/dev/null | sed 1q` _ACEOF -cat >>$CONFIG_STATUS <<_ACEOF +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ -\`$as_me' instantiates files from templates according to the -current configuration. +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. -Usage: $0 [OPTIONS] [FILE]... +Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit - -q, --quiet do not print progress messages + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions - --file=FILE[:TEMPLATE] - instantiate the configuration file FILE + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE Configuration files: $config_files -Report bugs to ." +Report bugs to the package provider." _ACEOF -cat >>$CONFIG_STATUS <<_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ config.status -configured by $0, generated by GNU Autoconf 2.61, - with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\" +configured by $0, generated by GNU Autoconf 2.65, + with options \\"\$ac_cs_config\\" -Copyright (C) 2006 Free Software Foundation, Inc. +Copyright (C) 2009 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' +test -n "\$AWK" || AWK=awk _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF -# If no file are specified by the user, then we need to provide default -# value. By we need to know if files were specified by the user. +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do @@ -2202,25 +2353,29 @@ do -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) - echo "$ac_cs_version"; exit ;; + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift - CONFIG_FILES="$CONFIG_FILES $ac_optarg" + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --he | --h | --help | --hel | -h ) - echo "$ac_cs_usage"; exit ;; + $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. - -*) { echo "$as_me: error: unrecognized option: $1 -Try \`$0 --help' for more information." >&2 - { (exit 1); exit 1; }; } ;; + -*) as_fn_error "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; - *) ac_config_targets="$ac_config_targets $1" + *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac @@ -2235,30 +2390,32 @@ if $ac_cs_silent; then fi _ACEOF -cat >>$CONFIG_STATUS <<_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then - echo "running CONFIG_SHELL=$SHELL $SHELL $0 "$ac_configure_args \$ac_configure_extra_args " --no-create --no-recursion" >&6 - CONFIG_SHELL=$SHELL + set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' export CONFIG_SHELL - exec $SHELL "$0"$ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + exec "\$@" fi _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX - echo "$ac_log" + $as_echo "$ac_log" } >&5 _ACEOF -cat >>$CONFIG_STATUS <<_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets @@ -2267,9 +2424,7 @@ do "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "src/config.sml") CONFIG_FILES="$CONFIG_FILES src/config.sml" ;; - *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5 -echo "$as_me: error: invalid argument: $ac_config_target" >&2;} - { (exit 1); exit 1; }; };; + *) as_fn_error "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done @@ -2294,7 +2449,7 @@ $debug || trap 'exit_status=$? { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status ' 0 - trap '{ (exit 1); exit 1; }' 1 2 13 15 + trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. @@ -2305,112 +2460,140 @@ $debug || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") -} || -{ - echo "$me: cannot create a temporary directory in ." >&2 - { (exit 1); exit 1; } -} - -# -# Set up the sed scripts for CONFIG_FILES section. -# +} || as_fn_error "cannot create a temporary directory in ." "$LINENO" 5 -# No need to generate the scripts if there are no CONFIG_FILES. -# This happens for instance when ./config.status config.h +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then -_ACEOF +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$tmp/subs1.awk" && +_ACEOF +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '$'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do - cat >conf$$subs.sed <<_ACEOF -SHELL!$SHELL$ac_delim -PATH_SEPARATOR!$PATH_SEPARATOR$ac_delim -PACKAGE_NAME!$PACKAGE_NAME$ac_delim -PACKAGE_TARNAME!$PACKAGE_TARNAME$ac_delim -PACKAGE_VERSION!$PACKAGE_VERSION$ac_delim -PACKAGE_STRING!$PACKAGE_STRING$ac_delim -PACKAGE_BUGREPORT!$PACKAGE_BUGREPORT$ac_delim -exec_prefix!$exec_prefix$ac_delim -prefix!$prefix$ac_delim -program_transform_name!$program_transform_name$ac_delim -bindir!$bindir$ac_delim -sbindir!$sbindir$ac_delim -libexecdir!$libexecdir$ac_delim -datarootdir!$datarootdir$ac_delim -datadir!$datadir$ac_delim -sysconfdir!$sysconfdir$ac_delim -sharedstatedir!$sharedstatedir$ac_delim -localstatedir!$localstatedir$ac_delim -includedir!$includedir$ac_delim -oldincludedir!$oldincludedir$ac_delim -docdir!$docdir$ac_delim -infodir!$infodir$ac_delim -htmldir!$htmldir$ac_delim -dvidir!$dvidir$ac_delim -pdfdir!$pdfdir$ac_delim -psdir!$psdir$ac_delim -libdir!$libdir$ac_delim -localedir!$localedir$ac_delim -mandir!$mandir$ac_delim -DEFS!$DEFS$ac_delim -ECHO_C!$ECHO_C$ac_delim -ECHO_N!$ECHO_N$ac_delim -ECHO_T!$ECHO_T$ac_delim -LIBS!$LIBS$ac_delim -build_alias!$build_alias$ac_delim -host_alias!$host_alias$ac_delim -target_alias!$target_alias$ac_delim -BIN!$BIN$ac_delim -LIB!$LIB$ac_delim -INCLUDE!$INCLUDE$ac_delim -SITELISP!$SITELISP$ac_delim -GCCARGS!$GCCARGS$ac_delim -do_not_edit!$do_not_edit$ac_delim -LIBOBJS!$LIBOBJS$ac_delim -LTLIBOBJS!$LTLIBOBJS$ac_delim -_ACEOF + . ./conf$$subs.sh || + as_fn_error "could not make $CONFIG_STATUS" "$LINENO" 5 - if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 45; then + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then - { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 -echo "$as_me: error: could not make $CONFIG_STATUS" >&2;} - { (exit 1); exit 1; }; } + as_fn_error "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done +rm -f conf$$subs.sh -ac_eof=`sed -n '/^CEOF[0-9]*$/s/CEOF/0/p' conf$$subs.sed` -if test -n "$ac_eof"; then - ac_eof=`echo "$ac_eof" | sort -nru | sed 1q` - ac_eof=`expr $ac_eof + 1` -fi +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} -cat >>$CONFIG_STATUS <<_ACEOF -cat >"\$tmp/subs-1.sed" <<\CEOF$ac_eof -/@[a-zA-Z_][a-zA-Z_0-9]*@/!b end +_ACAWK _ACEOF -sed ' -s/[,\\&]/\\&/g; s/@/@|#_!!_#|/g -s/^/s,@/; s/!/@,|#_!!_#|/ -:n -t n -s/'"$ac_delim"'$/,g/; t -s/$/\\/; p -N; s/^.*\n//; s/[,\\&]/\\&/g; s/@/@|#_!!_#|/g; b n -' >>$CONFIG_STATUS >$CONFIG_STATUS <<_ACEOF -:end -s/|#_!!_#|//g -CEOF$ac_eof +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \ + || as_fn_error "could not setup config files machinery" "$LINENO" 5 _ACEOF - # VPATH may cause trouble with some makes, so we remove $(srcdir), # ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty @@ -2426,20 +2609,20 @@ s/^[^=]*=[ ]*$// }' fi -cat >>$CONFIG_STATUS <<\_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" -for ac_tag in :F $CONFIG_FILES +eval set X " :F $CONFIG_FILES " +shift +for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; - :L* | :C*:*) { { echo "$as_me:$LINENO: error: Invalid tag $ac_tag." >&5 -echo "$as_me: error: Invalid tag $ac_tag." >&2;} - { (exit 1); exit 1; }; };; + :L* | :C*:*) as_fn_error "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac @@ -2467,26 +2650,34 @@ echo "$as_me: error: Invalid tag $ac_tag." >&2;} [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || - { { echo "$as_me:$LINENO: error: cannot find input file: $ac_f" >&5 -echo "$as_me: error: cannot find input file: $ac_f" >&2;} - { (exit 1); exit 1; }; };; + as_fn_error "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac - ac_file_inputs="$ac_file_inputs $ac_f" + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ - configure_input="Generated from "`IFS=: - echo $* | sed 's|^[^:]*/||;s|:[^:]*/|, |g'`" by configure." + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" - { echo "$as_me:$LINENO: creating $ac_file" >&5 -echo "$as_me: creating $ac_file" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac case $ac_tag in - *:-:* | *:-) cat >"$tmp/stdin";; + *:-:* | *:-) cat >"$tmp/stdin" \ + || as_fn_error "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac @@ -2496,42 +2687,7 @@ $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || -echo X"$ac_file" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - { as_dir="$ac_dir" - case $as_dir in #( - -*) as_dir=./$as_dir;; - esac - test -d "$as_dir" || { $as_mkdir_p && mkdir -p "$as_dir"; } || { - as_dirs= - while :; do - case $as_dir in #( - *\'*) as_qdir=`echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #( - *) as_qdir=$as_dir;; - esac - as_dirs="'$as_qdir' $as_dirs" - as_dir=`$as_dirname -- "$as_dir" || -$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$as_dir" : 'X\(//\)[^/]' \| \ - X"$as_dir" : 'X\(//\)$' \| \ - X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -echo X"$as_dir" | +$as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -2549,20 +2705,15 @@ echo X"$as_dir" | q } s/.*/./; q'` - test -d "$as_dir" && break - done - test -z "$as_dirs" || eval "mkdir $as_dirs" - } || test -d "$as_dir" || { { echo "$as_me:$LINENO: error: cannot create directory $as_dir" >&5 -echo "$as_me: error: cannot create directory $as_dir" >&2;} - { (exit 1); exit 1; }; }; } + as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'` + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,/..,g;s,/,,'` + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -2598,12 +2749,12 @@ ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= - -case `sed -n '/datarootdir/ { +ac_sed_dataroot=' +/datarootdir/ { p q } @@ -2611,36 +2762,37 @@ case `sed -n '/datarootdir/ { /@docdir@/p /@infodir@/p /@localedir@/p -/@mandir@/p -' $ac_file_inputs` in +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) - { echo "$as_me:$LINENO: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 -echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF -cat >>$CONFIG_STATUS <<_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g - s&\\\${datarootdir}&$datarootdir&g' ;; + s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? -cat >>$CONFIG_STATUS <<_ACEOF - sed "$ac_vpsub +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub $extrasub _ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b -s&@configure_input@&$configure_input&;t t +s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t @@ -2649,21 +2801,24 @@ s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t $ac_datarootdir_hack -" $ac_file_inputs | sed -f "$tmp/subs-1.sed" >$tmp/out +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \ + || as_fn_error "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } && - { echo "$as_me:$LINENO: WARNING: $ac_file contains a reference to the variable \`datarootdir' + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined." >&5 -echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined." >&2;} rm -f "$tmp/stdin" case $ac_file in - -) cat "$tmp/out"; rm -f "$tmp/out";; - *) rm -f "$ac_file"; mv "$tmp/out" $ac_file;; - esac + -) cat "$tmp/out" && rm -f "$tmp/out";; + *) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";; + esac \ + || as_fn_error "could not create $ac_file" "$LINENO" 5 ;; @@ -2679,11 +2834,13 @@ which seems to be undefined. Please make sure it is defined." >&2;} done # for ac_tag -{ (exit 0); exit 0; } +as_fn_exit 0 _ACEOF -chmod +x $CONFIG_STATUS ac_clean_files=$ac_clean_files_save +test $ac_write_fail = 0 || + as_fn_error "write failure creating $CONFIG_STATUS" "$LINENO" 5 + # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. @@ -2703,7 +2860,11 @@ if test "$no_create" != yes; then exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. - $ac_cs_success || { (exit 1); exit 1; } + $ac_cs_success || as_fn_exit $? +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi diff --git a/doc/manual.tex b/doc/manual.tex index 5c197116..3b7ce214 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -242,6 +242,8 @@ fastcgi.server = ( \end{itemize} \item \texttt{-sql FILENAME}: Set where a database set-up SQL script is written. + +\item \texttt{-static}: Link the runtime system statically. The default is to link against dynamic libraries. \end{itemize} diff --git a/src/cgi.sml b/src/cgi.sml index f57cd845..f853a12c 100644 --- a/src/cgi.sml +++ b/src/cgi.sml @@ -30,7 +30,8 @@ structure Cgi :> CGI = struct open Settings val () = addProtocol {name = "cgi", - link = clibFile "request.o" ^ " " ^ clibFile "cgi.o", + linkStatic = clibFile "cgi.o", + linkDynamic = "-lurweb_cgi", persistent = false} end diff --git a/src/compiler.sml b/src/compiler.sml index a596a21d..1ef8c5b1 100644 --- a/src/compiler.sml +++ b/src/compiler.sml @@ -1025,13 +1025,17 @@ val toSqlify = transform sqlify "sqlify" o toMono_opt2 fun compileC {cname, oname, ename, libs, profile, debug, link = link'} = let val proto = Settings.currentProtocol () - val urweb_o = clibFile "urweb.o" - val memmem_o = clibFile "memmem.o" + + val lib = if Settings.getStaticLinking () then + clibFile "request.o" ^ " " ^ clibFile "queue.o" ^ " " ^ clibFile "urweb.o" + ^ " " ^ clibFile "memmem.o" ^ " " ^ #linkStatic proto + else + "-L" ^ Config.libC ^ " -lurweb " ^ #linkDynamic proto val compile = "gcc " ^ Config.gccArgs ^ " -Wimplicit -Werror -O3 -fno-inline -I " ^ Config.includ ^ " -c " ^ cname ^ " -o " ^ oname - val link = "gcc -Werror -O3 -lm -lmhash -pthread " ^ Config.gccArgs ^ " " ^ libs ^ " " ^ urweb_o ^ " " ^ oname - ^ " " ^ memmem_o ^ " " ^ #link proto ^ " -o " ^ ename + val link = "gcc -Werror -O3 -lm -lmhash -pthread " ^ Config.gccArgs ^ " " ^ libs ^ " " ^ lib ^ " " ^ oname + ^ " -o " ^ ename val (compile, link) = if profile then diff --git a/src/fastcgi.sml b/src/fastcgi.sml index fbd24b5d..16836f30 100644 --- a/src/fastcgi.sml +++ b/src/fastcgi.sml @@ -30,7 +30,8 @@ structure Fastcgi :> FASTCGI = struct open Settings val () = addProtocol {name = "fastcgi", - link = clibFile "request.o" ^ " " ^ clibFile "queue.o" ^ " " ^ clibFile "fastcgi.o", + linkStatic = clibFile "fastcgi.o", + linkDynamic = "-lurweb_fastcgi", persistent = true} end diff --git a/src/http.sml b/src/http.sml index b835a4ac..3f6fc2df 100644 --- a/src/http.sml +++ b/src/http.sml @@ -30,7 +30,8 @@ structure Http :> HTTP = struct open Settings val () = addProtocol {name = "http", - link = clibFile "request.o" ^ " " ^ clibFile "queue.o" ^ " " ^ clibFile "http.o", + linkStatic = clibFile "http.o", + linkDynamic = "-lurweb_http", persistent = true} val () = setProtocol "http" diff --git a/src/main.mlton.sml b/src/main.mlton.sml index 36d0ce98..42f05259 100644 --- a/src/main.mlton.sml +++ b/src/main.mlton.sml @@ -63,6 +63,9 @@ fun doArgs args = | "-sql" :: s :: rest => (Settings.setSql (SOME s); doArgs rest) + | "-static" :: rest => + (Settings.setStaticLinking true; + doArgs rest) | arg :: rest => (if size arg > 0 andalso String.sub (arg, 0) = #"-" then raise Fail ("Unknown flag " ^ arg) diff --git a/src/settings.sig b/src/settings.sig index 574832a2..8eb4bc13 100644 --- a/src/settings.sig +++ b/src/settings.sig @@ -92,9 +92,10 @@ signature SETTINGS = sig (* Web protocols that generated programs may speak *) type protocol = { - name : string, (* Call it this on the command line *) - link : string, (* Pass these linker arguments *) - persistent : bool (* Multiple requests per process? *) + name : string, (* Call it this on the command line *) + linkStatic : string, (* Pass these static linker arguments *) + linkDynamic : string,(* Pass these dynamic linker arguments *) + persistent : bool (* Multiple requests per process? *) } val addProtocol : protocol -> unit val setProtocol : string -> unit @@ -182,4 +183,7 @@ signature SETTINGS = sig val setMonoInline : int -> unit val getMonoInline : unit -> int + val setStaticLinking : bool -> unit + val getStaticLinking : unit -> bool + end diff --git a/src/settings.sml b/src/settings.sml index a7f2cc9f..39398490 100644 --- a/src/settings.sml +++ b/src/settings.sml @@ -270,7 +270,8 @@ val checkMime = check type protocol = { name : string, - link : string, + linkStatic : string, + linkDynamic : string, persistent : bool } val protocols = ref ([] : protocol list) @@ -281,7 +282,8 @@ fun clibFile s = OS.Path.joinDirFile {dir = Config.libC, file = s} val curProto = ref {name = "", - link = "", + linkStatic = "", + linkDynamic = "", persistent = false} fun setProtocol name = case getProtocol name of @@ -427,4 +429,8 @@ val monoInline = ref 20 fun setMonoInline n = monoInline := n fun getMonoInline () = !monoInline +val staticLinking = ref false +fun setStaticLinking b = staticLinking := b +fun getStaticLinking () = !staticLinking + end -- cgit v1.2.3 From d662dfd8d76d24b5ecbbff9503c84e908c538301 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 29 Dec 2009 13:34:03 -0500 Subject: ctextarea; s/header/include in the manual --- doc/manual.tex | 4 ++-- lib/js/urweb.js | 9 +++++++++ lib/ur/basis.urs | 3 +++ src/monoize.sml | 23 +++++++++++++++++++++++ tests/ctextarea.ur | 8 ++++++++ tests/ctextarea.urp | 3 +++ tests/ctextarea.urs | 1 + 7 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 tests/ctextarea.ur create mode 100644 tests/ctextarea.urp create mode 100644 tests/ctextarea.urs (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 3b7ce214..07f96398 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -140,7 +140,7 @@ Here is the complete list of directive forms. ``FFI'' stands for ``foreign func \item \texttt{effectful Module.ident} registers an FFI function or transaction as having side effects. The optimizer avoids removing, moving, or duplicating calls to such functions. Every effectful FFI function must be registered, or the optimizer may make invalid transformations. \item \texttt{exe FILENAME} sets the filename to which to write the output executable. The default for file \texttt{P.urp} is \texttt{P.exe}. \item \texttt{ffi FILENAME} reads the file \texttt{FILENAME.urs} to determine the interface to a new FFI module. The name of the module is calculated from \texttt{FILENAME} in the same way as for normal source files. See the files \texttt{include/urweb.h} and \texttt{src/c/urweb.c} for examples of C headers and implementations for FFI modules. In general, every type or value \texttt{Module.ident} becomes \texttt{uw\_Module\_ident} in C. -\item \texttt{header FILENAME} adds \texttt{FILENAME} to the list of files to be \texttt{\#include}d in C sources. This is most useful for interfacing with new FFI modules. +\item \texttt{include FILENAME} adds \texttt{FILENAME} to the list of files to be \texttt{\#include}d in C sources. This is most useful for interfacing with new FFI modules. \item \texttt{jsFunc Module.ident=name} gives the JavaScript name of an FFI value. \item \texttt{library FILENAME} parses \texttt{FILENAME.urp} and merges its contents with the rest of the current file's contents. If \texttt{FILENAME.urp} doesn't exist, the compiler also tries \texttt{FILENAME/lib.urp}. \item \texttt{link FILENAME} adds \texttt{FILENAME} to the list of files to be passed to the GCC linker at the end of compilation. This is most useful for importing extra libraries needed by new FFI modules. @@ -1992,7 +1992,7 @@ It is most convenient to encapsulate an FFI binding with a new \texttt{.urp} fil \item \texttt{clientToServer Module.ident} declares a type as OK to marshal between clients and servers. By default, abstract FFI types are not allowed to be marshalled, since your library might be maintaining invariants that the simple serialization code doesn't check. \item \texttt{effectful Module.ident} registers a function that can have side effects. It is important to remember to use this directive for each such function, or else the optimizer might change program semantics. \item \texttt{ffi FILE.urs} names the file giving your library's signature. You can include multiple such files in a single \texttt{.urp} file, and each file \texttt{mod.urp} defines an FFI module \texttt{Mod}. -\item \texttt{header FILE} requests inclusion of a C header file. +\item \texttt{include FILE} requests inclusion of a C header file. \item \texttt{jsFunc Module.ident=name} gives a mapping from an Ur name for a value to a JavaScript name. \item \texttt{link FILE} requests that \texttt{FILE} be linked into applications. It should be a C object or library archive file, and you are responsible for generating it with your own build process. \item \texttt{script URL} requests inclusion of a JavaScript source file within application HTML. diff --git a/lib/js/urweb.js b/lib/js/urweb.js index 474bdaf6..3a0e7f9f 100644 --- a/lib/js/urweb.js +++ b/lib/js/urweb.js @@ -501,6 +501,15 @@ function chk(s) { return x; } +function tbx(s) { + var x = input(document.createElement("textarea"), s, + function(x) { return function(v) { if (x.innerHTML != v) x.innerHTML = v; }; }); + x.innerHTML = s.data; + x.onkeyup = function() { sv(s, x.value) }; + + return x; +} + function addOnChange(x, f) { var old = x.onchange; x.onchange = function() { old(); f (); }; diff --git a/lib/ur/basis.urs b/lib/ur/basis.urs index 02ac0126..ffce96c0 100644 --- a/lib/ur/basis.urs +++ b/lib/ur/basis.urs @@ -734,6 +734,9 @@ con cselect = [Cselect] val cselect : cformTag ([Source = source string, Onchange = transaction unit] ++ boxAttrs) cselect val coption : unit -> tag [Value = string, Selected = bool] cselect [] [] [] +val ctextarea : cformTag ([Value = string, Rows = int, Cols = int, Source = source string, Onchange = transaction unit, + Ontext = transaction unit] ++ boxAttrs) [] + (*** Tables *) val tabl : other ::: {Unit} -> [other ~ [Body, Table]] => unit diff --git a/src/monoize.sml b/src/monoize.sml index e9d90ecc..0f03111c 100644 --- a/src/monoize.sml +++ b/src/monoize.sml @@ -2985,6 +2985,29 @@ fun monoExp (env, st, fm) (all as (e, loc)) = | "coption" => normal ("option", NONE, NONE) + | "ctextarea" => + (case List.find (fn ("Source", _, _) => true | _ => false) attrs of + NONE => + let + val (ts, fm) = tagStart "textarea" + in + ((L'.EStrcat (ts, + (L'.EPrim (Prim.String " />"), loc)), + loc), fm) + end + | SOME (_, src, _) => + let + val sc = strcat [str "tbx(exec(", + (L'.EJavaScript (L'.Script, src), loc), + str "))"] + val sc = setAttrs sc + in + (strcat [str ""], + fm) + end) + | "tabl" => normal ("table", NONE, NONE) | _ => normal (tag, NONE, NONE)) end diff --git a/tests/ctextarea.ur b/tests/ctextarea.ur new file mode 100644 index 00000000..86a8c739 --- /dev/null +++ b/tests/ctextarea.ur @@ -0,0 +1,8 @@ +fun main () = + s <- source "DEFAULT"; + return +
+
+ + +
diff --git a/tests/ctextarea.urp b/tests/ctextarea.urp new file mode 100644 index 00000000..2ed9b69f --- /dev/null +++ b/tests/ctextarea.urp @@ -0,0 +1,3 @@ +debug + +ctextarea diff --git a/tests/ctextarea.urs b/tests/ctextarea.urs new file mode 100644 index 00000000..6ac44e0b --- /dev/null +++ b/tests/ctextarea.urs @@ -0,0 +1 @@ +val main : unit -> transaction page -- cgit v1.2.3 From b52a0b1b4485024429c3f51bf8a9e82ec5ebe2ca Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Wed, 30 Dec 2009 09:48:32 -0500 Subject: Update manual's description of implicit arguments --- doc/manual.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 07f96398..1f7a774b 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -465,7 +465,7 @@ A signature item or declaration $\mt{type} \; x$ or $\mt{type} \; x = \tau$ is e A signature item or declaration $\mt{class} \; x = \lambda y \Rightarrow c$ may be abbreviated $\mt{class} \; x \; y = c$. -Handling of implicit and explicit constructor arguments may be tweaked with some prefixes to variable references. An expression $@x$ is a version of $x$ where all implicit constructor arguments have been made explicit. An expression $@@x$ achieves the same effect, additionally halting automatic resolution of type class instances and automatic proving of disjointness constraints. The default is that any prefix of a variable's type consisting only of implicit polymorphism, type class instances, and disjointness obligations is resolved automatically, with the variable treated as having the type that starts after the last implicit element, with suitable unification variables substituted. The same syntax works for variables projected out of modules and for capitalized variables (datatype constructors). +Handling of implicit and explicit constructor arguments may be tweaked with some prefixes to variable references. An expression $@x$ is a version of $x$ where all implicit constructor arguments have been made explicit. An expression $@@x$ achieves the same effect, additionally halting automatic resolution of type class instances and automatic proving of disjointness constraints. The default is that implicit arguments are inserted automatically after any reference to a non-local variable, or after any application of a non-local variable to one or more arguments. For such an expression, implicit wildcard arguments are added for the longest prefix of the expression's type consisting only of implicit polymorphism, type class instances, and disjointness obligations. The same syntax works for variables projected out of modules and for capitalized variables (datatype constructors). At the expression level, an analogue is available of the composite $\lambda$ form for constructors. We define the language of binders as $b ::= p \mid [x] \mid [x \; ? \; \kappa] \mid X \mid [c \sim c]$. A lone variable $[x]$ stands for an implicit constructor variable of unspecified kind. The standard value-level function binder is recovered as the type-annotated pattern form $x : \tau$. It is a compile-time error to include a pattern $p$ that does not match every value of the appropriate type. -- cgit v1.2.3 From 33116639b54ce9d71cb6ca85c185ed56908da505 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 12 Jan 2010 09:42:13 -0500 Subject: New release --- CHANGELOG | 9 +++++++++ doc/manual.tex | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index a893024c..ac96d69a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,12 @@ +======== +20100112 +======== + +- Basis.serialized type family, for storing more types in the database +- Basis.textBlob, for building blobs from strings +- Basis.debug function, for server-side debug printing +- Bug fixes & optimization improvements + ======== 20091230 ======== diff --git a/doc/manual.tex b/doc/manual.tex index 1f7a774b..5849f744 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1249,7 +1249,7 @@ $$\begin{array}{l} \mt{datatype} \; \mt{list} \; \mt{t} = \mt{Nil} \mid \mt{Cons} \; \mt{of} \; \mt{t} \times \mt{list} \; \mt{t} \end{array}$$ -The only unusual element of this list is the $\mt{blob}$ type, which stands for binary sequences. +The only unusual element of this list is the $\mt{blob}$ type, which stands for binary sequences. Simple blobs can be created from strings via $\mt{Basis.textBlob}$. Blobs will also be generated from HTTP file uploads. Another important generic Ur element comes at the beginning of \texttt{top.urs}. @@ -1293,6 +1293,11 @@ $$\begin{array}{l} \mt{val} \; \mt{transaction\_monad} : \mt{monad} \; \mt{transaction} \end{array}$$ +For debugging purposes, a transactional function is provided for outputting a string on the server process' \texttt{stderr}. +$$\begin{array}{l} + \mt{val} \; \mt{debug} : \mt{string} \to \mt{transaction} \; \mt{unit} +\end{array}$$ + \subsection{HTTP} There are transactions for reading an HTTP header by name and for getting and setting strongly-typed cookies. Cookies may only be created by the $\mt{cookie}$ declaration form, ensuring that they be named consistently based on module structure. @@ -1556,6 +1561,14 @@ $$\begin{array}{l} \hspace{.1in} \to \mt{t} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{t} \end{array}$$ +Additionally, most function-free types may be injected safely, via the $\mt{serialized}$ type family. +$$\begin{array}{l} + \mt{con} \; \mt{serialized} :: \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{serialize} : \mt{t} ::: \mt{Type} \to \mt{t} \to \mt{serialized} \; \mt{t} \\ + \mt{val} \; \mt{deserialize} : \mt{t} ::: \mt{Type} \to \mt{serialized} \; \mt{t} \to \mt{t} \\ + \mt{val} \; \mt{sql\_serialized} : \mt{t} ::: \mt{Type} \to \mt{sql\_injectable\_prim} \; (\mt{serialized} \; \mt{t}) +\end{array}$$ + We have the SQL nullness test, which is necessary because of the strange SQL semantics of equality in the presence of null values. $$\begin{array}{l} \mt{val} \; \mt{sql\_is\_null} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \\ -- cgit v1.2.3 From 8eab6ba5a16002cd6e5ddfb6a09cb23b351c9d32 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 12 Jan 2010 11:19:02 -0500 Subject: Update manual for last two changesets --- doc/manual.tex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 5849f744..e2ce39be 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -205,6 +205,8 @@ sqlite3 path/to/database/file Date: Sat, 30 Jan 2010 08:45:31 -0500 Subject: New release --- CHANGELOG | 11 +++++++++++ doc/manual.tex | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index ac96d69a..fa9a26e5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +======== +20100130 +======== + +- Conversion to an Automake-based build system, for greater portability in + building shared libraries +- -path and -root command-line flags +- Exported page handling functions (i.e., those page-generating functions + appearing in the main module's signature) may now take any number of + arguments, including 0. + ======== 20100112 ======== diff --git a/doc/manual.tex b/doc/manual.tex index e2ce39be..8d888295 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -91,7 +91,16 @@ Even with the right packages installed, configuration and building might fail to GCCARGS=-fnested-functions ./configure \end{verbatim} -Some OSX users have reported needing to use this particular GCCARGS value. +Some Mac OS X users have reported needing to use this particular GCCARGS value. + +Since the author is still getting a handle on the GNU Autotools that provide the build system, you may need to do some further work to get started, especially in environments with significant differences from Linux (where most testing is done). One OS X user reported needing to run \texttt{./configure} with \texttt{CFLAGS=-I/opt/local/include}, since this directory wound up holding a header file associated with a \texttt{libmhash} package installed via DarwinPorts. While that user built Ur/Web successfully with no further tweaks, another OS X user reported that he needed to install Autoconf and Automake from MacPorts and run the following before \texttt{./configure}: + +\begin{verbatim} +aclocal +autoconf +\end{verbatim} + +He also reported needing to add \texttt{/opt/local/bin/} to his \texttt{\$PATH}, because of an unusual set-up where Autotools files were installed in that directory. The Emacs mode can be set to autoload by adding the following to your \texttt{.emacs} file. -- cgit v1.2.3 From ab0611f75f34be35567af66528ca78c3cf48a77a Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 31 Jan 2010 07:44:49 -0500 Subject: Remove mention of (hopefully) fixed problem with ./configure --- doc/manual.tex | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 8d888295..b15d4b6b 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -93,14 +93,7 @@ GCCARGS=-fnested-functions ./configure Some Mac OS X users have reported needing to use this particular GCCARGS value. -Since the author is still getting a handle on the GNU Autotools that provide the build system, you may need to do some further work to get started, especially in environments with significant differences from Linux (where most testing is done). One OS X user reported needing to run \texttt{./configure} with \texttt{CFLAGS=-I/opt/local/include}, since this directory wound up holding a header file associated with a \texttt{libmhash} package installed via DarwinPorts. While that user built Ur/Web successfully with no further tweaks, another OS X user reported that he needed to install Autoconf and Automake from MacPorts and run the following before \texttt{./configure}: - -\begin{verbatim} -aclocal -autoconf -\end{verbatim} - -He also reported needing to add \texttt{/opt/local/bin/} to his \texttt{\$PATH}, because of an unusual set-up where Autotools files were installed in that directory. +Since the author is still getting a handle on the GNU Autotools that provide the build system, you may need to do some further work to get started, especially in environments with significant differences from Linux (where most testing is done). One OS X user reported needing to run \texttt{./configure} with \texttt{CFLAGS=-I/opt/local/include}, since this directory wound up holding a header file associated with a \texttt{libmhash} package installed via DarwinPorts. The Emacs mode can be set to autoload by adding the following to your \texttt{.emacs} file. -- cgit v1.2.3 From a978113ec89492e3c612e2409b8dfd0f3c9682cb Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 6 Feb 2010 15:34:41 -0500 Subject: Allow .urp files without initial blank lines --- demo/alert.urp | 1 - demo/batchFun.urp | 1 - demo/cookie.urp | 1 - demo/counter.urp | 1 - demo/form.urp | 1 - demo/hello.urp | 1 - demo/link.urp | 1 - demo/listEdit.urp | 1 - demo/listShop.urp | 1 - demo/metaform1.urp | 1 - demo/metaform2.urp | 1 - demo/nested.urp | 1 - demo/react.urp | 1 - demo/rec.urp | 1 - demo/subforms.urp | 1 - demo/sum.urp | 1 - demo/tcSum.urp | 1 - demo/threads.urp | 1 - doc/manual.tex | 2 +- src/compiler.sml | 25 +++++++++++++++++++++++-- src/demo.sml | 9 ++++++--- 21 files changed, 30 insertions(+), 24 deletions(-) (limited to 'doc') diff --git a/demo/alert.urp b/demo/alert.urp index 5a85a04e..34016b99 100644 --- a/demo/alert.urp +++ b/demo/alert.urp @@ -1,2 +1 @@ - alert diff --git a/demo/batchFun.urp b/demo/batchFun.urp index 48f4d27a..a646565e 100644 --- a/demo/batchFun.urp +++ b/demo/batchFun.urp @@ -1,2 +1 @@ - batchFun diff --git a/demo/cookie.urp b/demo/cookie.urp index 9e283d4b..871bb87e 100644 --- a/demo/cookie.urp +++ b/demo/cookie.urp @@ -1,2 +1 @@ - cookie diff --git a/demo/counter.urp b/demo/counter.urp index d22312c9..e64bdbfb 100644 --- a/demo/counter.urp +++ b/demo/counter.urp @@ -1,2 +1 @@ - counter diff --git a/demo/form.urp b/demo/form.urp index 73356d49..335f1dd0 100644 --- a/demo/form.urp +++ b/demo/form.urp @@ -1,2 +1 @@ - form diff --git a/demo/hello.urp b/demo/hello.urp index 3e23ae48..ce013625 100644 --- a/demo/hello.urp +++ b/demo/hello.urp @@ -1,2 +1 @@ - hello diff --git a/demo/link.urp b/demo/link.urp index 01d60187..2b2328d7 100644 --- a/demo/link.urp +++ b/demo/link.urp @@ -1,2 +1 @@ - link diff --git a/demo/listEdit.urp b/demo/listEdit.urp index 592e0546..87bd2452 100644 --- a/demo/listEdit.urp +++ b/demo/listEdit.urp @@ -1,2 +1 @@ - listEdit diff --git a/demo/listShop.urp b/demo/listShop.urp index 85d318d4..9b4b3189 100644 --- a/demo/listShop.urp +++ b/demo/listShop.urp @@ -1,4 +1,3 @@ - list listFun listShop diff --git a/demo/metaform1.urp b/demo/metaform1.urp index 7f04b9b7..c5558e25 100644 --- a/demo/metaform1.urp +++ b/demo/metaform1.urp @@ -1,3 +1,2 @@ - metaform metaform1 diff --git a/demo/metaform2.urp b/demo/metaform2.urp index debc0448..623a84d5 100644 --- a/demo/metaform2.urp +++ b/demo/metaform2.urp @@ -1,3 +1,2 @@ - metaform metaform2 diff --git a/demo/nested.urp b/demo/nested.urp index 179014dc..79c53955 100644 --- a/demo/nested.urp +++ b/demo/nested.urp @@ -1,2 +1 @@ - nested diff --git a/demo/react.urp b/demo/react.urp index 80ed64e1..f7757f40 100644 --- a/demo/react.urp +++ b/demo/react.urp @@ -1,2 +1 @@ - react diff --git a/demo/rec.urp b/demo/rec.urp index 979d20d5..6e27e716 100644 --- a/demo/rec.urp +++ b/demo/rec.urp @@ -1,2 +1 @@ - rec diff --git a/demo/subforms.urp b/demo/subforms.urp index d2ac98ee..e70e4ef8 100644 --- a/demo/subforms.urp +++ b/demo/subforms.urp @@ -1,2 +1 @@ - subforms diff --git a/demo/sum.urp b/demo/sum.urp index e872a81b..92292873 100644 --- a/demo/sum.urp +++ b/demo/sum.urp @@ -1,2 +1 @@ - sum diff --git a/demo/tcSum.urp b/demo/tcSum.urp index 8b36efc0..42f743ac 100644 --- a/demo/tcSum.urp +++ b/demo/tcSum.urp @@ -1,2 +1 @@ - tcSum diff --git a/demo/threads.urp b/demo/threads.urp index 153e09a9..84fbe4f7 100644 --- a/demo/threads.urp +++ b/demo/threads.urp @@ -1,3 +1,2 @@ - buffer threads diff --git a/doc/manual.tex b/doc/manual.tex index b15d4b6b..c5cfc52f 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -128,7 +128,7 @@ createdb test psql -f crud1.sql test \end{verbatim} -A blank line always separates the named directives from a list of modules to include in the project; if there are no named directives, a blank line must begin the file. +A blank line separates the named directives from a list of modules to include in the project. For each entry \texttt{M} in the module list, the file \texttt{M.urs} is included in the project if it exists, and the file \texttt{M.ur} must exist and is always included. diff --git a/src/compiler.sml b/src/compiler.sml index e72d8b4b..ab3ec10a 100644 --- a/src/compiler.sml +++ b/src/compiler.sml @@ -282,7 +282,25 @@ fun parseUrp' accLibs fname = fun pu filename = let val dir = OS.Path.dir filename - val inf = TextIO.openIn (OS.Path.joinBaseExt {base = filename, ext = SOME "urp"}) + fun opener () = TextIO.openIn (OS.Path.joinBaseExt {base = filename, ext = SOME "urp"}) + + val inf = opener () + + fun hasAnyLine () = + case TextIO.inputLine inf of + NONE => false + | SOME "\n" => false + | _ => true + + fun hasBlankLine () = + case TextIO.inputLine inf of + NONE => false + | SOME "\n" => hasAnyLine () + | _ => hasBlankLine () + + val hasBlankLine = hasBlankLine () + + val inf = (TextIO.closeIn inf; opener ()) fun pathify fname = if size fname > 0 andalso String.sub (fname, 0) = #"$" then @@ -591,7 +609,10 @@ fun parseUrp' accLibs fname = read () end - val job = read () + val job = if hasBlankLine then + read () + else + finish (readSources []) in TextIO.closeIn inf; Settings.setUrlPrefix (#prefix job); diff --git a/src/demo.sml b/src/demo.sml index 55615173..71f72fbf 100644 --- a/src/demo.sml +++ b/src/demo.sml @@ -1,4 +1,4 @@ -(* Copyright (c) 2008, Adam Chlipala +(* Copyright (c) 2008-2010, Adam Chlipala * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -197,7 +197,8 @@ fun make' {prefix, dirname, guided} = ext = SOME s} val src' = OS.Path.file src in - if String.isPrefix (OS.Path.mkCanonical dirname) src + if String.isPrefix (OS.Path.mkAbsolute {path = dirname, + relativeTo = OS.FileSys.getDir ()}) src andalso OS.FileSys.access (src, []) then (TextIO.output (out, " | ")) else - () + print (src ^ " " + ^ OS.Path.mkAbsolute {path = dirname, + relativeTo = OS.FileSys.getDir ()} ^ "\n") end in ifEx "urs"; -- cgit v1.2.3 From 3c8845010c2fcb27db5844e89649a856df312fbf Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 7 Feb 2010 16:13:09 -0500 Subject: Expand 'row types' in first paragraph of manual --- doc/manual.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index c5cfc52f..34995e62 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -20,7 +20,7 @@ \section{Introduction} -\emph{Ur} is a programming language designed to introduce richer type system features into functional programming in the tradition of ML and Haskell. Ur is functional, pure, statically-typed, and strict. Ur supports a powerful kind of \emph{metaprogramming} based on \emph{row types}. +\emph{Ur} is a programming language designed to introduce richer type system features into functional programming in the tradition of ML and Haskell. Ur is functional, pure, statically-typed, and strict. Ur supports a powerful kind of \emph{metaprogramming} based on \emph{type-level computation with type-level records}. \emph{Ur/Web} is Ur plus a special standard library and associated rules for parsing and optimization. Ur/Web supports construction of dynamic web applications backed by SQL databases. The signature of the standard library is such that well-typed Ur/Web programs ``don't go wrong'' in a very broad sense. Not only do they not crash during particular page generations, but they also may not: -- cgit v1.2.3 From eceb1ba3c0c84f4536320aebb45466fb6b1b22d3 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 9 Feb 2010 09:51:21 -0500 Subject: Update manual with Gian's experience building in OS X --- doc/manual.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 34995e62..3b6f05f1 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -93,7 +93,7 @@ GCCARGS=-fnested-functions ./configure Some Mac OS X users have reported needing to use this particular GCCARGS value. -Since the author is still getting a handle on the GNU Autotools that provide the build system, you may need to do some further work to get started, especially in environments with significant differences from Linux (where most testing is done). One OS X user reported needing to run \texttt{./configure} with \texttt{CFLAGS=-I/opt/local/include}, since this directory wound up holding a header file associated with a \texttt{libmhash} package installed via DarwinPorts. +Since the author is still getting a handle on the GNU Autotools that provide the build system, you may need to do some further work to get started, especially in environments with significant differences from Linux (where most testing is done). One OS X user reported needing to run \texttt{./configure} with \texttt{CFLAGS=-I/opt/local/include}, since this directory wound up holding a header file associated with a \texttt{libmhash} package installed via DarwinPorts. Further, to get libpq to link, another user reported setting \texttt{GCCARGS="-I/opt/local/include -L/opt/local/lib/postgresql84"}, after creating a symbolic link with \texttt{ln -s /opt/local/include/postgresql84 /opt/local/include/postgresql}. The Emacs mode can be set to autoload by adding the following to your \texttt{.emacs} file. -- cgit v1.2.3 From 861dbf0153f3383666dc0f3c35675d0b9a625b8d Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 9 Feb 2010 20:08:59 -0500 Subject: Tips for CGI scripts without httpd.conf access --- doc/manual.tex | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 3b6f05f1..1e5980ba 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -220,6 +220,19 @@ sqlite3 path/to/database/file Date: Thu, 11 Feb 2010 09:10:01 -0500 Subject: sigfile directive --- doc/manual.tex | 5 +++++ src/c/urweb.c | 2 ++ src/cgi.sml | 15 ++++++++++++++- src/cjr_print.sml | 2 ++ src/compiler.sig | 3 ++- src/compiler.sml | 18 +++++++++++++++--- src/demo.sml | 3 ++- src/fastcgi.sml | 15 ++++++++++++++- src/http.sml | 16 +++++++++++++++- src/main.mlton.sml | 3 +++ src/settings.sig | 6 +++++- src/settings.sml | 10 ++++++++-- 12 files changed, 87 insertions(+), 11 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 1e5980ba..b6dddad6 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -152,6 +152,7 @@ Here is the complete list of directive forms. ``FFI'' stands for ``foreign func \item \texttt{rewrite KIND FROM TO} gives a rule for rewriting canonical module paths. For instance, the canonical path of a page may be \texttt{Mod1.Mod2.mypage}, while you would rather the page were accessed via a URL containing only \texttt{page}. The directive \texttt{rewrite url Mod1/Mod2/mypage page} would accomplish that. The possible values of \texttt{KIND} determine which kinds of objects are affected. The kind \texttt{all} matches any object, and \texttt{url} matches page URLs. The kinds \texttt{table}, \texttt{sequence}, and \texttt{view} match those sorts of SQL entities, and \texttt{relation} matches any of those three. \texttt{cookie} matches HTTP cookies, and \texttt{style} matches CSS class names. If \texttt{FROM} ends in \texttt{/*}, it is interpreted as a prefix matching rule, and rewriting occurs by replacing only the appropriate prefix of a path with \texttt{TO}. While the actual external names of relations and styles have parts separated by underscores instead of slashes, all rewrite rules must be written in terms of slashes. \item \texttt{script URL} adds \texttt{URL} to the list of extra JavaScript files to be included at the beginning of any page that uses JavaScript. This is most useful for importing JavaScript versions of functions found in new FFI modules. \item \texttt{serverOnly Module.ident} registers an FFI function or transaction that may only be run on the server. +\item \texttt{sigfile PATH} sets a path where your application should look for a key to use in cryptographic signing. This is used to prevent cross-site request forgery attacks for any form handler that both reads a cookie and creates side effects. If the referenced file doesn't exist, an application will create it and read its saved data on future invocations. You can also initialize the file manually with any contents at least 16 bytes long; the first 16 bytes will be treated as the key. \item \texttt{sql FILENAME} sets where to write an SQL file with the commands to create the expected database schema. The default is not to create such a file. \item \texttt{timeout N} sets to \texttt{N} seconds the amount of time that the generated server will wait after the last contact from a client before determining that that client has exited the application. Clients that remain active will take the timeout setting into account in determining how often to ping the server, so it only makes sense to set a high timeout to cope with browser and network delays and failures. Higher timeouts can lead to more unnecessary client information taking up memory on the server. The timeout goes unused by any page that doesn't involve the \texttt{recv} function, since the server only needs to store per-client information for clients that receive asynchronous messages. \end{itemize} @@ -233,6 +234,8 @@ prefix /dir/script.exe/ To access the \texttt{foo} function in the \texttt{Bar} module, you would then hit \texttt{http://somewhere/dir/script.exe/Bar/foo}. + If your application contains form handlers that read cookies before causing side effects, then you will need to use the \texttt{sigfile} \texttt{.urp} directive, too. + \item \texttt{fastcgi}: This is a newer protocol inspired by CGI, wherein web servers can start and reuse persistent external processes to generate dynamic content. Ur/Web doesn't implement the whole protocol, but Ur/Web's support has been tested to work with the \texttt{mod\_fastcgi}s of Apache and lighttpd. To configure a FastCGI program with Apache, one could combine the above \texttt{ScriptAlias} line with a line like this: @@ -260,6 +263,8 @@ fastcgi.server = ( \item \texttt{-root Name PATH}: Trigger an alternate module convention for all source files found in directory \texttt{PATH} or any of its subdirectories. Any file \texttt{PATH/foo.ur} defines a module \texttt{Name.Foo} instead of the usual \texttt{Foo}. Any file \texttt{PATH/subdir/foo.ur} defines a module \texttt{Name.Subdir.Foo}, and so on for arbitrary nesting of subdirectories. +\item \texttt{-sigfile PATH}: Same as the \texttt{sigfile} directive in \texttt{.urp} files + \item \texttt{-sql FILENAME}: Set where a database set-up SQL script is written. \item \texttt{-static}: Link the runtime system statically. The default is to link against dynamic libraries. diff --git a/src/c/urweb.c b/src/c/urweb.c index 7d0a95b2..7821a999 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -330,6 +330,7 @@ static void client_send(client *c, buf *msg) { // Global entry points +extern void uw_global_custom(); extern void uw_init_crypto(); void uw_global_init() { @@ -337,6 +338,7 @@ void uw_global_init() { clients = malloc(0); + uw_global_custom(); uw_init_crypto(); } diff --git a/src/cgi.sml b/src/cgi.sml index f4426a70..9099d429 100644 --- a/src/cgi.sml +++ b/src/cgi.sml @@ -28,11 +28,24 @@ structure Cgi :> CGI = struct open Settings +open Print.PD Print val () = addProtocol {name = "cgi", compile = "", linkStatic = Config.lib ^ "/../liburweb_cgi.a", linkDynamic = "-lurweb_cgi", - persistent = false} + persistent = false, + code = fn () => box [string "void uw_global_custom() {", + newline, + case getSigFile () of + NONE => box [] + | SOME sf => box [string "extern char *uw_sig_file;", + newline, + string "uw_sig_file = \"", + string sf, + string "\";", + newline], + string "}", + newline]} end diff --git a/src/cjr_print.sml b/src/cjr_print.sml index 360ecb5c..fae01db1 100644 --- a/src/cjr_print.sml +++ b/src/cjr_print.sml @@ -2805,6 +2805,8 @@ fun p_file env (ds, ps) = newline, newline, + #code (Settings.currentProtocol ()) (), + if hasDb then #init (Settings.currentDbms ()) {dbstring = !dbstring, prepared = !prepped, diff --git a/src/compiler.sig b/src/compiler.sig index fd1eccf8..78e82ba8 100644 --- a/src/compiler.sig +++ b/src/compiler.sig @@ -51,7 +51,8 @@ signature COMPILER = sig filterUrl : Settings.rule list, filterMime : Settings.rule list, protocol : string option, - dbms : string option + dbms : string option, + sigFile : string option } val compile : string -> bool val compiler : string -> unit diff --git a/src/compiler.sml b/src/compiler.sml index ba80e37e..99c730f1 100644 --- a/src/compiler.sml +++ b/src/compiler.sml @@ -55,7 +55,8 @@ type job = { filterUrl : Settings.rule list, filterMime : Settings.rule list, protocol : string option, - dbms : string option + dbms : string option, + sigFile : string option } type ('src, 'dst) phase = { @@ -379,6 +380,7 @@ fun parseUrp' accLibs fname = val libs = ref [] val protocol = ref NONE val dbms = ref NONE + val sigFile = ref (Settings.getSigFile ()) fun finish sources = let @@ -405,7 +407,8 @@ fun parseUrp' accLibs fname = filterMime = rev (!mime), sources = sources, protocol = !protocol, - dbms = !dbms + dbms = !dbms, + sigFile = !sigFile } fun mergeO f (old, new) = @@ -446,7 +449,8 @@ fun parseUrp' accLibs fname = @ List.filter (fn s => List.all (fn s' => s' <> s) (#sources new)) (#sources old), protocol = mergeO #2 (#protocol old, #protocol new), - dbms = mergeO #2 (#dbms old, #dbms new) + dbms = mergeO #2 (#dbms old, #dbms new), + sigFile = mergeO #2 (#sigFile old, #sigFile new) } in if accLibs then @@ -523,6 +527,14 @@ fun parseUrp' accLibs fname = (case !database of NONE => database := SOME arg | SOME _ => ()) + | "dbms" => + (case !dbms of + NONE => dbms := SOME arg + | SOME _ => ()) + | "sigfile" => + (case !sigFile of + NONE => sigFile := SOME arg + | SOME _ => ()) | "exe" => (case !exe of NONE => exe := SOME (relify arg) diff --git a/src/demo.sml b/src/demo.sml index bb79a749..6280400b 100644 --- a/src/demo.sml +++ b/src/demo.sml @@ -112,7 +112,8 @@ fun make' {prefix, dirname, guided} = filterUrl = #filterUrl combined @ #filterUrl urp, filterMime = #filterMime combined @ #filterMime urp, protocol = mergeWith #2 (#protocol combined, #protocol urp), - dbms = mergeWith #2 (#dbms combined, #dbms urp) + dbms = mergeWith #2 (#dbms combined, #dbms urp), + sigFile = mergeWith #2 (#sigFile combined, #sigFile urp) } val parse = Compiler.run (Compiler.transform Compiler.parseUrp "Demo parseUrp") diff --git a/src/fastcgi.sml b/src/fastcgi.sml index c2e81d92..31feaee9 100644 --- a/src/fastcgi.sml +++ b/src/fastcgi.sml @@ -28,11 +28,24 @@ structure Fastcgi :> FASTCGI = struct open Settings +open Print.PD Print val () = addProtocol {name = "fastcgi", compile = "", linkStatic = Config.lib ^ "/../liburweb_fastcgi.a", linkDynamic = "-lurweb_fastcgi", - persistent = true} + persistent = true, + code = fn () => box [string "void uw_global_custom() {", + newline, + case getSigFile () of + NONE => box [] + | SOME sf => box [string "extern char *uw_sig_file;", + newline, + string "uw_sig_file = \"", + string sf, + string "\";", + newline], + string "}", + newline]} end diff --git a/src/http.sml b/src/http.sml index 4054eb1e..a760e195 100644 --- a/src/http.sml +++ b/src/http.sml @@ -28,12 +28,26 @@ structure Http :> HTTP = struct open Settings +open Print.PD Print val () = addProtocol {name = "http", compile = "", linkStatic = Config.lib ^ "/../liburweb_http.a", linkDynamic = "-lurweb_http", - persistent = true} + persistent = true, + code = fn () => box [string "void uw_global_custom() {", + newline, + case getSigFile () of + NONE => box [] + | SOME sf => box [string "extern char *uw_sig_file;", + newline, + string "uw_sig_file = \"", + string sf, + string "\";", + newline], + string "}", + newline]} + val () = setProtocol "http" end diff --git a/src/main.mlton.sml b/src/main.mlton.sml index 9cf5064a..fc1ba7e5 100644 --- a/src/main.mlton.sml +++ b/src/main.mlton.sml @@ -72,6 +72,9 @@ fun doArgs args = | "-root" :: name :: root :: rest => (Compiler.addModuleRoot (root, name); doArgs rest) + | "-sigfile" :: name :: rest => + (Settings.setSigFile (SOME name); + doArgs rest) | arg :: rest => (if size arg > 0 andalso String.sub (arg, 0) = #"-" then raise Fail ("Unknown flag " ^ arg) diff --git a/src/settings.sig b/src/settings.sig index f3a4379e..348c47d4 100644 --- a/src/settings.sig +++ b/src/settings.sig @@ -96,7 +96,8 @@ signature SETTINGS = sig compile : string, (* Pass these `gcc -c' arguments *) linkStatic : string, (* Pass these static linker arguments *) linkDynamic : string,(* Pass these dynamic linker arguments *) - persistent : bool (* Multiple requests per process? *) + persistent : bool, (* Multiple requests per process? *) + code : unit -> Print.PD.pp_desc (* Extra code to include in C files *) } val addProtocol : protocol -> unit val setProtocol : string -> unit @@ -190,4 +191,7 @@ signature SETTINGS = sig val setDeadlines : bool -> unit val getDeadlines : unit -> bool + val setSigFile : string option -> unit + val getSigFile : unit -> string option + end diff --git a/src/settings.sml b/src/settings.sml index 0bbe3961..f600d2ac 100644 --- a/src/settings.sml +++ b/src/settings.sml @@ -275,7 +275,8 @@ type protocol = { compile : string, linkStatic : string, linkDynamic : string, - persistent : bool + persistent : bool, + code : unit -> Print.PD.pp_desc } val protocols = ref ([] : protocol list) fun addProtocol p = protocols := p :: !protocols @@ -288,7 +289,8 @@ val curProto = ref {name = "", compile = "", linkStatic = "", linkDynamic = "", - persistent = false} + persistent = false, + code = fn () => Print.box []} fun setProtocol name = case getProtocol name of NONE => raise Fail ("Unknown protocol " ^ name) @@ -441,4 +443,8 @@ val deadlines = ref false fun setDeadlines b = deadlines := b fun getDeadlines () = !deadlines +val sigFile = ref (NONE : string option) +fun setSigFile v = sigFile := v +fun getSigFile () = !sigFile + end -- cgit v1.2.3 From 1607362ff628b9a1476679fbf9146b66ce92a3ee Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 27 Feb 2010 14:57:57 -0500 Subject: Basic analysis of tag and CSS class usage --- doc/manual.tex | 6 ++ src/compiler.sig | 2 + src/compiler.sml | 7 ++ src/css.sig | 43 ++++++++ src/css.sml | 305 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.mlton.sml | 22 +++- src/sources | 3 + 7 files changed, 385 insertions(+), 3 deletions(-) create mode 100644 src/css.sig create mode 100644 src/css.sml (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index b6dddad6..6ac5a8d5 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -176,6 +176,12 @@ To stop the compilation process after type-checking, run urweb -tc P \end{verbatim} +To output information relevant to CSS stylesheets (and not finish regular compilation), run +\begin{verbatim} +urweb -css P +\end{verbatim} +The first output line is a list of categories of CSS properties that would be worth setting on the document body. The remaining lines are space-separated pairs of CSS class names and categories of properties that would be worth setting for that class. The category codes are divided into two varieties. Codes that reveal properties of a tag or its (recursive) children are \cd{B} for block-level elements, \cd{C} for table captions, \cd{D} for table cells, \cd{L} for lists, and \cd{T} for tables. Codes that reveal properties of the precise tag that uses a class are \cd{b} for block-level elements, \cd{t} for tables, \cd{d} for table cells, \cd{-} for table rows, \cd{H} for the possibility to set a height, \cd{N} for non-replaced inline-level elements, \cd{R} for replaced inline elements, and \cd{W} for the possibility to set a width. + Some other command-line parameters are accepted: \begin{itemize} \item \texttt{-db }: Set database connection information, using the format expected by Postgres's \texttt{PQconnectdb()}, which is \texttt{name1=value1 ... nameN=valueN}. The same format is also parsed and used to discover connection parameters for MySQL and SQLite. The only significant settings for MySQL are \texttt{host}, \texttt{hostaddr}, \texttt{port}, \texttt{dbname}, \texttt{user}, and \texttt{password}. The only significant setting for SQLite is \texttt{dbname}, which is interpreted as the filesystem path to the database. Additionally, when using SQLite, a database string may be just a file path. diff --git a/src/compiler.sig b/src/compiler.sig index 78e82ba8..3d77a4cd 100644 --- a/src/compiler.sig +++ b/src/compiler.sig @@ -91,6 +91,7 @@ signature COMPILER = sig val specialize : (Core.file, Core.file) phase val marshalcheck : (Core.file, Core.file) phase val effectize : (Core.file, Core.file) phase + val css : (Core.file, Css.report) phase val monoize : (Core.file, Mono.file) phase val mono_opt : (Mono.file, Mono.file) phase val untangle : (Mono.file, Mono.file) phase @@ -131,6 +132,7 @@ signature COMPILER = sig val toShake5 : (string, Core.file) transform val toMarshalcheck : (string, Core.file) transform val toEffectize : (string, Core.file) transform + val toCss : (string, Css.report) transform val toMonoize : (string, Mono.file) transform val toMono_opt1 : (string, Mono.file) transform val toUntangle : (string, Mono.file) transform diff --git a/src/compiler.sml b/src/compiler.sml index 99c730f1..c74a0915 100644 --- a/src/compiler.sml +++ b/src/compiler.sml @@ -1001,6 +1001,13 @@ val effectize = { val toEffectize = transform effectize "effectize" o toMarshalcheck +val css = { + func = Css.summarize, + print = fn _ => Print.box [] +} + +val toCss = transform css "css" o toShake5 + val monoize = { func = Monoize.monoize CoreEnv.empty, print = MonoPrint.p_file MonoEnv.empty diff --git a/src/css.sig b/src/css.sig new file mode 100644 index 00000000..c7243cf7 --- /dev/null +++ b/src/css.sig @@ -0,0 +1,43 @@ +(* Copyright (c) 2010, Adam Chlipala + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - The names of contributors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *) + +signature CSS = sig + + datatype inheritable = Block | List | Table | Caption | Td + datatype others = OBlock | OTable | OTd | Tr | NonReplacedInline | ReplacedInline | Width | Height + + val inheritableToString : inheritable -> string + val othersToString : others -> string + + type summary = inheritable list * others list + + type report = {Overall : inheritable list, + Classes : (string * summary) list} + + val summarize : Core.file -> report + +end diff --git a/src/css.sml b/src/css.sml new file mode 100644 index 00000000..7189904f --- /dev/null +++ b/src/css.sml @@ -0,0 +1,305 @@ +(* Copyright (c) 2010, Adam Chlipala + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - The names of contributors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *) + +structure Css :> CSS = struct + +structure IM = IntBinaryMap + +structure SM = BinaryMapFn(struct + type ord_key = string + val compare = String.compare + end) + +datatype inheritable = Block | List | Table | Caption | Td +datatype others = OBlock | OTable | OTd | Tr | NonReplacedInline | ReplacedInline | Width | Height + +fun inheritableToString x = + case x of + Block => "B" + | List => "L" + | Table => "T" + | Caption => "C" + | Td => "D" + +fun othersToString x = + case x of + OBlock => "b" + | OTable => "t" + | OTd => "d" + | Tr => "-" + | NonReplacedInline => "N" + | ReplacedInline => "R" + | Width => "W" + | Height => "H" + +type summary = inheritable list * others list + +fun merge' (ls1, ls2) = foldl (fn (x, ls) => if List.exists (fn y => y = x) ls then ls else x :: ls) ls2 ls1 +fun merge ((in1, ot1), (in2, ot2)) = (merge' (in1, in2), merge' (ot1, ot2)) +fun mergePC {parent = (in1, ot1), child = in2} = (merge' (in1, in2), ot1) + +val nada = ([], []) +val block = ([Block], [OBlock, Width, Height]) +val inline = ([], [NonReplacedInline]) +val list = ([Block, List], [OBlock, Width, Height]) +val replaced = ([], [ ReplacedInline, Width, Height]) +val table = ([Block, Table], [OBlock, OTable, Width, Height]) +val tr = ([Block], [OBlock, Tr, Width]) +val td = ([Block, Td], [OBlock, OTd, Height]) + +val tags = [("span", inline), + ("div", block), + ("p", block), + ("b", inline), + ("i", inline), + ("tt", inline), + ("h1", block), + ("h2", block), + ("h3", block), + ("h4", block), + ("h5", block), + ("h6", block), + ("li", list), + ("ol", list), + ("ul", list), + ("hr", block), + ("a", inline), + ("img", replaced), + ("form", block), + ("hidden", replaced), + ("textbox", replaced), + ("password", replaced), + ("textarea", replaced), + ("checkbox", replaced), + ("upload", replaced), + ("radio", replaced), + ("select", replaced), + ("submit", replaced), + ("label", inline), + ("ctextbox", replaced), + ("button", replaced), + ("ccheckbox", replaced), + ("cselect", replaced), + ("ctextarea", replaced), + ("tabl", table), + ("tr", tr), + ("th", td), + ("td", td)] + +val tags = foldl (fn ((tag, css), tags) => + SM.insert (tags, tag, css)) SM.empty tags + +open Core + +fun summarize file = + let + fun decl ((d, _), st as (globals, classes)) = + let + fun getTag (e, _) = + case e of + EFfi ("Basis", tag) => SOME tag + | ECApp (e, _) => getTag e + | EApp (e, _) => getTag e + | _ => NONE + + fun exp ((e, _), classes) = + case e of + EPrim _ => ([], classes) + | ERel _ => ([], classes) + | ENamed n => + (case IM.find (globals, n) of + NONE => [] + | SOME (_, sm) => sm, + classes) + | ECon (_, _, _, NONE) => ([], classes) + | ECon (_, _, _, SOME e) => exp (e, classes) + | EFfi _ => ([], classes) + | EFfiApp (_, _, es) => expList (es, classes) + + | EApp ( + (EApp ( + (EApp ( + (EApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (EFfi ("Basis", "tag"), + _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), + (ECon (_, _, _, SOME (ENamed class, _)), _)), _), + attrs), _), + tag), _), + xml) => + let + val (sm, classes) = exp (xml, classes) + val (sm', classes) = exp (attrs, classes) + val sm = merge' (sm, sm') + in + case getTag tag of + NONE => (sm, classes) + | SOME tag => + case SM.find (tags, tag) of + NONE => (sm, classes) + | SOME sm' => + let + val sm'' = mergePC {parent = sm', child = sm} + val old = Option.getOpt (IM.find (classes, class), nada) + val classes = IM.insert (classes, class, merge (old, sm'')) + in + (merge' (#1 sm', sm), classes) + end + end + + | EApp ( + (EApp ( + (EApp ( + (EApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (ECApp ( + (EFfi ("Basis", "tag"), + _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), + _), _), + attrs), _), + tag), _), + xml) => + let + val (sm, classes) = exp (xml, classes) + val (sm', classes) = exp (attrs, classes) + val sm = merge' (sm, sm') + in + case getTag tag of + NONE => (sm, classes) + | SOME tag => + case SM.find (tags, tag) of + NONE => (sm, classes) + | SOME sm' => (merge' (#1 sm', sm), classes) + end + + | EApp (e1, e2) => + let + val (sm1, classes) = exp (e1, classes) + val (sm2, classes) = exp (e2, classes) + in + (merge' (sm1, sm2), classes) + end + | EAbs (_, _, _, e) => exp (e, classes) + | ECApp (e, _) => exp (e, classes) + | ECAbs (_, _, e) => exp (e, classes) + | EKAbs (_, e) => exp (e, classes) + | EKApp (e, _) => exp (e, classes) + | ERecord xets => expList (map #2 xets, classes) + | EField (e, _, _) => exp (e, classes) + | EConcat (e1, _, e2, _) => + let + val (sm1, classes) = exp (e1, classes) + val (sm2, classes) = exp (e2, classes) + in + (merge' (sm1, sm2), classes) + end + | ECut (e, _, _) => exp (e, classes) + | ECutMulti (e, _, _) => exp (e, classes) + | ECase (e, pes, _) => + let + val (sm, classes) = exp (e, classes) + val (sms, classes) = expList (map #2 pes, classes) + in + (merge' (sm, sms), classes) + end + | EWrite e => exp (e, classes) + | EClosure (_, es) => expList (es, classes) + | ELet (_, _, e1, e2) => + let + val (sm1, classes) = exp (e1, classes) + val (sm2, classes) = exp (e2, classes) + in + (merge' (sm1, sm2), classes) + end + | EServerCall (_, es, _) => expList (es, classes) + + and expList (es, classes) = foldl (fn (e, (sm, classes)) => + let + val (sm', classes) = exp (e, classes) + in + (merge' (sm, sm'), classes) + end) ([], classes) es + in + case d of + DCon _ => st + | DDatatype _ => st + | DVal (_, n, _, e, _) => + let + val (sm, classes) = exp (e, classes) + in + (IM.insert (globals, n, (NONE, sm)), classes) + end + | DValRec vis => + let + val (sm, classes) = foldl (fn ((_, _, _, e, _), + (sm, classes)) => + let + val (sm', classes) = exp (e, classes) + in + (merge' (sm', sm), classes) + end) ([], classes) vis + in + (foldl (fn ((_, n, _, _, _), globals) => IM.insert (globals, n, (NONE, sm))) globals vis, + classes) + end + | DExport _ => st + | DTable _ => st + | DSequence _ => st + | DView _ => st + | DDatabase _ => st + | DCookie _ => st + | DStyle (_, n, s) => (IM.insert (globals, n, (SOME s, [])), classes) + | DTask _ => st + end + + val (globals, classes) = foldl decl (IM.empty, IM.empty) file + in + {Overall = IM.foldl (fn ((_, sm), sm') => merge' (sm, sm')) [] globals, + Classes = ListMergeSort.sort (fn ((s1, _), (s2, _)) => String.compare (s1, s2) = GREATER) + (List.mapPartial (fn (i, sm) => + case IM.find (globals, i) of + SOME (SOME s, _) => SOME (s, sm) + | _ => NONE) (IM.listItemsi classes))} + end + +type report = {Overall : inheritable list, + Classes : (string * summary) list} + +end diff --git a/src/main.mlton.sml b/src/main.mlton.sml index fc1ba7e5..5676cd9c 100644 --- a/src/main.mlton.sml +++ b/src/main.mlton.sml @@ -29,10 +29,14 @@ val timing = ref false val tc = ref false val sources = ref ([] : string list) val demo = ref (NONE : (string * bool) option) +val css = ref false fun doArgs args = case args of [] => () + | "-css" :: rest => + (css := true; + doArgs rest) | "-demo" :: prefix :: rest => (demo := SOME (prefix, false); doArgs rest) @@ -90,10 +94,22 @@ val job = | _ => raise Fail "Zero or multiple job files specified" val () = - case !demo of - SOME (prefix, guided) => + case (!css, !demo) of + (true, _) => + (case Compiler.run Compiler.toCss job of + NONE => OS.Process.exit OS.Process.failure + | SOME {Overall = ov, Classes = cl} => + (app (print o Css.inheritableToString) ov; + print "\n"; + app (fn (x, (ins, ots)) => + (print x; + print " "; + app (print o Css.inheritableToString) ins; + app (print o Css.othersToString) ots; + print "\n")) cl)) + | (_, SOME (prefix, guided)) => Demo.make {prefix = prefix, dirname = job, guided = guided} - | NONE => + | _ => if !tc then (Compiler.check Compiler.toElaborate job; if ErrorMsg.anyErrors () then diff --git a/src/sources b/src/sources index ddc7deff..67c2e45a 100644 --- a/src/sources +++ b/src/sources @@ -140,6 +140,9 @@ effectize.sml marshalcheck.sig marshalcheck.sml +css.sig +css.sml + mono.sml mono_util.sig -- cgit v1.2.3 From f59bbf0b942cd888c798c06ba6841bf94562a438 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 27 Feb 2010 16:49:11 -0500 Subject: benignEffectful --- doc/manual.tex | 1 + src/compiler.sig | 1 + src/compiler.sml | 9 ++++++++- src/demo.sml | 1 + src/mono_reduce.sml | 6 +++--- src/settings.sig | 4 ++++ src/settings.sml | 33 +++++++++++++++++++-------------- 7 files changed, 37 insertions(+), 18 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 6ac5a8d5..d9d40919 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -135,6 +135,7 @@ For each entry \texttt{M} in the module list, the file \texttt{M.urs} is include Here is the complete list of directive forms. ``FFI'' stands for ``foreign function interface,'' Ur's facility for interaction between Ur programs and C and JavaScript libraries. \begin{itemize} \item \texttt{[allow|deny] [url|mime] PATTERN} registers a rule governing which URLs or MIME types are allowed in this application. The first such rule to match a URL or MIME type determines the verdict. If \texttt{PATTERN} ends in \texttt{*}, it is interpreted as a prefix rule. Otherwise, a string must match it exactly. +\item \texttt{benignEffectful Module.ident} registers an FFI function or transaction as having side effects. The optimizer avoids removing, moving, or duplicating calls to such functions. Every effectful FFI function must be registered, or the optimizer may make invalid transformations. This version of the \texttt{effectful} directive registers that this function has only session-local side effects. \item \texttt{clientOnly Module.ident} registers an FFI function or transaction that may only be run in client browsers. \item \texttt{clientToServer Module.ident} adds FFI type \texttt{Module.ident} to the list of types that are OK to marshal from clients to servers. Values like XML trees and SQL queries are hard to marshal without introducing expensive validity checks, so it's easier to ensure that the server never trusts clients to send such values. The file \texttt{include/urweb.h} shows examples of the C support functions that are required of any type that may be marshalled. These include \texttt{attrify}, \texttt{urlify}, and \texttt{unurlify} functions. \item \texttt{database DBSTRING} sets the string to pass to libpq to open a database connection. diff --git a/src/compiler.sig b/src/compiler.sig index 3d77a4cd..63da4e5c 100644 --- a/src/compiler.sig +++ b/src/compiler.sig @@ -44,6 +44,7 @@ signature COMPILER = sig scripts : string list, clientToServer : Settings.ffi list, effectful : Settings.ffi list, + benignEffectful : Settings.ffi list, clientOnly : Settings.ffi list, serverOnly : Settings.ffi list, jsFuncs : (Settings.ffi * string) list, diff --git a/src/compiler.sml b/src/compiler.sml index c74a0915..f0313c8b 100644 --- a/src/compiler.sml +++ b/src/compiler.sml @@ -48,6 +48,7 @@ type job = { scripts : string list, clientToServer : Settings.ffi list, effectful : Settings.ffi list, + benignEffectful : Settings.ffi list, clientOnly : Settings.ffi list, serverOnly : Settings.ffi list, jsFuncs : (Settings.ffi * string) list, @@ -212,7 +213,7 @@ val parseUr = { fun p_job ({prefix, database, exe, sql, sources, debug, profile, timeout, ffi, link, headers, scripts, - clientToServer, effectful, clientOnly, serverOnly, jsFuncs, ...} : job) = + clientToServer, effectful, benignEffectful, clientOnly, serverOnly, jsFuncs, ...} : job) = let open Print.PD open Print @@ -248,6 +249,7 @@ fun p_job ({prefix, database, exe, sql, sources, debug, profile, p_list_sep (box []) (fn s => box [string "Link", space, string s, newline]) link, p_ffi "ClientToServer" clientToServer, p_ffi "Effectful" effectful, + p_ffi "BenignEffectful" benignEffectful, p_ffi "ClientOnly" clientOnly, p_ffi "ServerOnly" serverOnly, p_list_sep (box []) (fn ((m, s), s') => @@ -371,6 +373,7 @@ fun parseUrp' accLibs fname = val scripts = ref [] val clientToServer = ref [] val effectful = ref [] + val benignEffectful = ref [] val clientOnly = ref [] val serverOnly = ref [] val jsFuncs = ref [] @@ -399,6 +402,7 @@ fun parseUrp' accLibs fname = scripts = rev (!scripts), clientToServer = rev (!clientToServer), effectful = rev (!effectful), + benignEffectful = rev (!benignEffectful), clientOnly = rev (!clientOnly), serverOnly = rev (!serverOnly), jsFuncs = rev (!jsFuncs), @@ -439,6 +443,7 @@ fun parseUrp' accLibs fname = scripts = #scripts old @ #scripts new, clientToServer = #clientToServer old @ #clientToServer new, effectful = #effectful old @ #effectful new, + benignEffectful = #benignEffectful old @ #benignEffectful new, clientOnly = #clientOnly old @ #clientOnly new, serverOnly = #serverOnly old @ #serverOnly new, jsFuncs = #jsFuncs old @ #jsFuncs new, @@ -564,6 +569,7 @@ fun parseUrp' accLibs fname = | "script" => scripts := arg :: !scripts | "clientToServer" => clientToServer := ffiS () :: !clientToServer | "effectful" => effectful := ffiS () :: !effectful + | "benignEffectful" => benignEffectful := ffiS () :: !benignEffectful | "clientOnly" => clientOnly := ffiS () :: !clientOnly | "serverOnly" => serverOnly := ffiS () :: !serverOnly | "jsFunc" => jsFuncs := ffiM () :: !jsFuncs @@ -626,6 +632,7 @@ fun parseUrp' accLibs fname = Settings.setScripts (#scripts job); Settings.setClientToServer (#clientToServer job); Settings.setEffectful (#effectful job); + Settings.setBenignEffectful (#benignEffectful job); Settings.setClientOnly (#clientOnly job); Settings.setServerOnly (#serverOnly job); Settings.setJsFuncs (#jsFuncs job); diff --git a/src/demo.sml b/src/demo.sml index 6280400b..6ae64264 100644 --- a/src/demo.sml +++ b/src/demo.sml @@ -105,6 +105,7 @@ fun make' {prefix, dirname, guided} = scripts = [], clientToServer = [], effectful = [], + benignEffectful = [], clientOnly = [], serverOnly = [], jsFuncs = [], diff --git a/src/mono_reduce.sml b/src/mono_reduce.sml index 10de1c56..6bd5ceb8 100644 --- a/src/mono_reduce.sml +++ b/src/mono_reduce.sml @@ -52,7 +52,7 @@ fun simpleImpure (tsyms, syms) = | EDml _ => true | ENextval _ => true | ESetval _ => true - | EFfiApp (m, x, _) => Settings.isEffectful (m, x) + | EFfiApp (m, x, _) => Settings.isEffectful (m, x) orelse Settings.isBenignEffectful (m, x) | EServerCall _ => true | ERecv _ => true | ESleep _ => true @@ -87,7 +87,7 @@ fun impure (e, _) = | ENone _ => false | ESome (_, e) => impure e | EFfi _ => false - | EFfiApp (m, x, _) => Settings.isEffectful (m, x) + | EFfiApp (m, x, _) => Settings.isEffectful (m, x) orelse Settings.isBenignEffectful (m, x) | EApp ((EFfi _, _), _) => false | EApp _ => true @@ -372,7 +372,7 @@ fun reduce file = | ESome (_, e) => summarize d e | EFfi _ => [] | EFfiApp (m, x, es) => - if Settings.isEffectful (m, x) then + if Settings.isEffectful (m, x) orelse Settings.isBenignEffectful (m, x) then List.concat (map (summarize d) es) @ [Unsure] else List.concat (map (summarize d) es) diff --git a/src/settings.sig b/src/settings.sig index 348c47d4..3179c229 100644 --- a/src/settings.sig +++ b/src/settings.sig @@ -58,6 +58,10 @@ signature SETTINGS = sig val setEffectful : ffi list -> unit val isEffectful : ffi -> bool + (* Which FFI functions should not have their calls removed or reordered, but cause no lasting effects? *) + val setBenignEffectful : ffi list -> unit + val isBenignEffectful : ffi -> bool + (* Which FFI functions may only be run in clients? *) val setClientOnly : ffi list -> unit val isClientOnly : ffi -> bool diff --git a/src/settings.sml b/src/settings.sml index f600d2ac..cd081725 100644 --- a/src/settings.sml +++ b/src/settings.sml @@ -80,28 +80,33 @@ fun mayClientToServer x = S.member (!clientToServer, x) val effectfulBase = basis ["dml", "nextval", "setval", - "get_cookie", "set_cookie", "clear_cookie", - "new_client_source", - "get_client_source", - "set_client_source", - "current", - "alert", "new_channel", - "send", - "onError", - "onFail", - "onConnectFail", - "onDisconnect", - "onServerError", - "kc", - "debug"] + "send"] val effectful = ref effectfulBase fun setEffectful ls = effectful := S.addList (effectfulBase, ls) fun isEffectful x = S.member (!effectful, x) +val benignBase = basis ["get_cookie", + "new_client_source", + "get_client_source", + "set_client_source", + "current", + "alert", + "onError", + "onFail", + "onConnectFail", + "onDisconnect", + "onServerError", + "kc", + "debug"] + +val benign = ref benignBase +fun setBenignEffectful ls = benign := S.addList (benignBase, ls) +fun isBenignEffectful x = S.member (!benign, x) + val clientBase = basis ["get", "set", "current", -- cgit v1.2.3 From ba73bb0f4dc54d67c55f0d8c74ebe8ac62344217 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 9 Mar 2010 18:28:44 -0500 Subject: safeGet --- CHANGELOG | 6 ++++++ doc/manual.tex | 1 + src/compiler.sig | 5 +++-- src/compiler.sml | 13 +++++++++---- src/demo.sml | 3 ++- src/effectize.sml | 7 ++++++- src/settings.sig | 4 ++++ src/settings.sml | 9 +++++++++ 8 files changed, 40 insertions(+), 8 deletions(-) (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index 5f83e495..58ecd03d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +======== +Next +======== + +- safeGet .urp directive + ======== 20100213 ======== diff --git a/doc/manual.tex b/doc/manual.tex index d9d40919..6d2324db 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -151,6 +151,7 @@ Here is the complete list of directive forms. ``FFI'' stands for ``foreign func \item \texttt{prefix PREFIX} sets the prefix included before every URI within the generated application. The default is \texttt{/}. \item \texttt{profile} generates an executable that may be used with gprof. \item \texttt{rewrite KIND FROM TO} gives a rule for rewriting canonical module paths. For instance, the canonical path of a page may be \texttt{Mod1.Mod2.mypage}, while you would rather the page were accessed via a URL containing only \texttt{page}. The directive \texttt{rewrite url Mod1/Mod2/mypage page} would accomplish that. The possible values of \texttt{KIND} determine which kinds of objects are affected. The kind \texttt{all} matches any object, and \texttt{url} matches page URLs. The kinds \texttt{table}, \texttt{sequence}, and \texttt{view} match those sorts of SQL entities, and \texttt{relation} matches any of those three. \texttt{cookie} matches HTTP cookies, and \texttt{style} matches CSS class names. If \texttt{FROM} ends in \texttt{/*}, it is interpreted as a prefix matching rule, and rewriting occurs by replacing only the appropriate prefix of a path with \texttt{TO}. While the actual external names of relations and styles have parts separated by underscores instead of slashes, all rewrite rules must be written in terms of slashes. +\item \texttt{safeGet URI} asks to allow the page handler assigned this canonical URI prefix to cause persistent side effects, even if accessed via an HTTP \cd{GET} request. \item \texttt{script URL} adds \texttt{URL} to the list of extra JavaScript files to be included at the beginning of any page that uses JavaScript. This is most useful for importing JavaScript versions of functions found in new FFI modules. \item \texttt{serverOnly Module.ident} registers an FFI function or transaction that may only be run on the server. \item \texttt{sigfile PATH} sets a path where your application should look for a key to use in cryptographic signing. This is used to prevent cross-site request forgery attacks for any form handler that both reads a cookie and creates side effects. If the referenced file doesn't exist, an application will create it and read its saved data on future invocations. You can also initialize the file manually with any contents at least 16 bytes long; the first 16 bytes will be treated as the key. diff --git a/src/compiler.sig b/src/compiler.sig index 6be2b22f..0516d97e 100644 --- a/src/compiler.sig +++ b/src/compiler.sig @@ -1,4 +1,4 @@ -(* Copyright (c) 2008-2009, Adam Chlipala +(* Copyright (c) 2008-2010, Adam Chlipala * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,7 +53,8 @@ signature COMPILER = sig filterMime : Settings.rule list, protocol : string option, dbms : string option, - sigFile : string option + sigFile : string option, + safeGets : string list } val compile : string -> bool val compiler : string -> unit diff --git a/src/compiler.sml b/src/compiler.sml index 237cad08..5a97f13d 100644 --- a/src/compiler.sml +++ b/src/compiler.sml @@ -57,7 +57,8 @@ type job = { filterMime : Settings.rule list, protocol : string option, dbms : string option, - sigFile : string option + sigFile : string option, + safeGets : string list } type ('src, 'dst) phase = { @@ -385,6 +386,7 @@ fun parseUrp' accLibs fname = val protocol = ref NONE val dbms = ref NONE val sigFile = ref (Settings.getSigFile ()) + val safeGets = ref [] fun finish sources = let @@ -413,7 +415,8 @@ fun parseUrp' accLibs fname = sources = sources, protocol = !protocol, dbms = !dbms, - sigFile = !sigFile + sigFile = !sigFile, + safeGets = rev (!safeGets) } fun mergeO f (old, new) = @@ -456,7 +459,8 @@ fun parseUrp' accLibs fname = (#sources old), protocol = mergeO #2 (#protocol old, #protocol new), dbms = mergeO #2 (#dbms old, #dbms new), - sigFile = mergeO #2 (#sigFile old, #sigFile new) + sigFile = mergeO #2 (#sigFile old, #sigFile new), + safeGets = #safeGets old @ #safeGets new } in if accLibs then @@ -569,7 +573,7 @@ fun parseUrp' accLibs fname = | "include" => headers := relifyA arg :: !headers | "script" => scripts := arg :: !scripts | "clientToServer" => clientToServer := ffiS () :: !clientToServer - | "effectful" => effectful := ffiS () :: !effectful + | "safeGet" => safeGets := arg :: !safeGets | "benignEffectful" => benignEffectful := ffiS () :: !benignEffectful | "clientOnly" => clientOnly := ffiS () :: !clientOnly | "serverOnly" => serverOnly := ffiS () :: !serverOnly @@ -642,6 +646,7 @@ fun parseUrp' accLibs fname = Settings.setMimeRules (#filterMime job); Option.app Settings.setProtocol (#protocol job); Option.app Settings.setDbms (#dbms job); + Settings.setSafeGets (#safeGets job); job end in diff --git a/src/demo.sml b/src/demo.sml index 6ae64264..a67411de 100644 --- a/src/demo.sml +++ b/src/demo.sml @@ -114,7 +114,8 @@ fun make' {prefix, dirname, guided} = filterMime = #filterMime combined @ #filterMime urp, protocol = mergeWith #2 (#protocol combined, #protocol urp), dbms = mergeWith #2 (#dbms combined, #dbms urp), - sigFile = mergeWith #2 (#sigFile combined, #sigFile urp) + sigFile = mergeWith #2 (#sigFile combined, #sigFile urp), + safeGets = [] } val parse = Compiler.run (Compiler.transform Compiler.parseUrp "Demo parseUrp") diff --git a/src/effectize.sml b/src/effectize.sml index 1685fbe9..7f148476 100644 --- a/src/effectize.sml +++ b/src/effectize.sml @@ -143,7 +143,12 @@ fun effectize file = | DExport (Link, n, _) => (case IM.find (writers, n) of NONE => () - | SOME (loc, s) => ErrorMsg.errorAt loc ("A link (" ^ s ^ ") could cause side effects; try implementing it with a form instead"); + | SOME (loc, s) => + if Settings.isSafeGet s then + () + else + ErrorMsg.errorAt loc ("A link (" ^ s + ^ ") could cause side effects; try implementing it with a form instead"); ((DExport (Link, n, IM.inDomain (pushers, n)), #2 d), evs)) | DExport (Action _, n, _) => ((DExport (Action (if IM.inDomain (writers, n) then diff --git a/src/settings.sig b/src/settings.sig index 3179c229..94472eb1 100644 --- a/src/settings.sig +++ b/src/settings.sig @@ -198,4 +198,8 @@ signature SETTINGS = sig val setSigFile : string option -> unit val getSigFile : unit -> string option + (* Which GET-able functions should be allowed to have side effects? *) + val setSafeGets : string list -> unit + val isSafeGet : string -> bool + end diff --git a/src/settings.sml b/src/settings.sml index cd081725..97c16675 100644 --- a/src/settings.sml +++ b/src/settings.sml @@ -452,4 +452,13 @@ val sigFile = ref (NONE : string option) fun setSigFile v = sigFile := v fun getSigFile () = !sigFile +structure SS = BinarySetFn(struct + type ord_key = string + val compare = String.compare + end) + +val safeGet = ref SS.empty +fun setSafeGets ls = safeGet := SS.addList (SS.empty, ls) +fun isSafeGet x = SS.member (!safeGet, x) + end -- cgit v1.2.3 From 147060e49808e92b1ae5c0c9f967fecc75627c5c Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 16 Mar 2010 16:02:54 -0400 Subject: Update manual for COUNT(col) --- CHANGELOG | 7 ++++++- doc/manual.tex | 20 ++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index 58ecd03d..2289dbb2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,12 @@ Next ======== -- safeGet .urp directive +- -verbose flag +- COUNT(col) SQL aggregate function +- 'benignEffectful' and 'safeGet' .urp commands +- Remove Basis.getRequestHeader, since it can be used to circumvent cookie + security +- Bug fixes and optimization improvements ======== 20100213 diff --git a/doc/manual.tex b/doc/manual.tex index 6d2324db..027c929d 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1647,17 +1647,21 @@ $$\begin{array}{l} \end{array}$$ $$\begin{array}{l} - \mt{con} \; \mt{sql\_aggregate} :: \mt{Type} \to \mt{Type} \\ - \mt{val} \; \mt{sql\_aggregate} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \\ - \hspace{.1in} \to \mt{sql\_aggregate} \; \mt{t} \to \mt{sql\_exp} \; \mt{agg} \; \mt{agg} \; \mt{exps} \; \mt{t} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{t} + \mt{con} \; \mt{sql\_aggregate} :: \mt{Type} \to \mt{Type} \to \mt{Type} \\ + \mt{val} \; \mt{sql\_aggregate} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{dom} ::: \mt{Type} \to \mt{ran} ::: \mt{Type} \\ + \hspace{.1in} \to \mt{sql\_aggregate} \; \mt{dom} \; \mt{ran} \to \mt{sql\_exp} \; \mt{agg} \; \mt{agg} \; \mt{exps} \; \mt{dom} \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{ran} +\end{array}$$ + +$$\begin{array}{l} + \mt{val} \; \mt{sql\_count\_col} : \mt{t} ::: \mt{Type} \to \mt{sql\_aggregate} \; (\mt{option} \; \mt{t}) \; \mt{int} \end{array}$$ $$\begin{array}{l} \mt{class} \; \mt{sql\_summable} \\ \mt{val} \; \mt{sql\_summable\_int} : \mt{sql\_summable} \; \mt{int} \\ \mt{val} \; \mt{sql\_summable\_float} : \mt{sql\_summable} \; \mt{float} \\ - \mt{val} \; \mt{sql\_avg} : \mt{t} ::: \mt{Type} \to \mt{sql\_summable} \; \mt{t} \to \mt{sql\_aggregate} \; \mt{t} \\ - \mt{val} \; \mt{sql\_sum} : \mt{t} ::: \mt{Type} \to \mt{sql\_summable} \mt{t} \to \mt{sql\_aggregate} \; \mt{t} + \mt{val} \; \mt{sql\_avg} : \mt{t} ::: \mt{Type} \to \mt{sql\_summable} \; \mt{t} \to \mt{sql\_aggregate} \; \mt{t} \; \mt{t} \\ + \mt{val} \; \mt{sql\_sum} : \mt{t} ::: \mt{Type} \to \mt{sql\_summable} \mt{t} \to \mt{sql\_aggregate} \; \mt{t} \; \mt{t} \end{array}$$ $$\begin{array}{l} @@ -1666,8 +1670,8 @@ $$\begin{array}{l} \mt{val} \; \mt{sql\_maxable\_float} : \mt{sql\_maxable} \; \mt{float} \\ \mt{val} \; \mt{sql\_maxable\_string} : \mt{sql\_maxable} \; \mt{string} \\ \mt{val} \; \mt{sql\_maxable\_time} : \mt{sql\_maxable} \; \mt{time} \\ - \mt{val} \; \mt{sql\_max} : \mt{t} ::: \mt{Type} \to \mt{sql\_maxable} \; \mt{t} \to \mt{sql\_aggregate} \; \mt{t} \\ - \mt{val} \; \mt{sql\_min} : \mt{t} ::: \mt{Type} \to \mt{sql\_maxable} \; \mt{t} \to \mt{sql\_aggregate} \; \mt{t} + \mt{val} \; \mt{sql\_max} : \mt{t} ::: \mt{Type} \to \mt{sql\_maxable} \; \mt{t} \to \mt{sql\_aggregate} \; \mt{t} \; \mt{t} \\ + \mt{val} \; \mt{sql\_min} : \mt{t} ::: \mt{Type} \to \mt{sql\_maxable} \; \mt{t} \to \mt{sql\_aggregate} \; \mt{t} \; \mt{t} \end{array}$$ \texttt{FROM} clauses are specified using a type family. @@ -1968,7 +1972,7 @@ $$\begin{array}{rrcll} \textrm{Nullary operators} & n &::=& \mt{CURRENT\_TIMESTAMP} \\ \textrm{Unary operators} & u &::=& \mt{NOT} \\ \textrm{Binary operators} & b &::=& \mt{AND} \mid \mt{OR} \mid \neq \mid < \mid \leq \mid > \mid \geq \\ - \textrm{Aggregate functions} & a &::=& \mt{AVG} \mid \mt{SUM} \mid \mt{MIN} \mid \mt{MAX} \\ + \textrm{Aggregate functions} & a &::=& \mt{COUNT} \mid \mt{AVG} \mid \mt{SUM} \mid \mt{MIN} \mid \mt{MAX} \\ \textrm{Directions} & o &::=& \mt{ASC} \mid \mt{DESC} \\ \textrm{SQL integer} & N &::=& n \mid \{e\} \\ \end{array}$$ -- cgit v1.2.3 From a987a1ae26ee20ba32cfb85a0d38797067619105 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 25 Mar 2010 16:27:10 -0400 Subject: Add subqueries to the manual --- doc/manual.tex | 78 ++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 30 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 027c929d..eea0a01a 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1487,30 +1487,32 @@ Section \ref{tables} shows the expanded syntax of the $\mt{table}$ declaration a \subsubsection{Queries} -A final query is constructed via the $\mt{sql\_query}$ function. Constructor arguments respectively specify the table fields we select (as records mapping tables to the subsets of their fields that we choose) and the (always named) extra expressions that we select. +A final query is constructed via the $\mt{sql\_query}$ function. Constructor arguments respectively specify the free table variables (which will only be available in subqueries), table fields we select (as records mapping tables to the subsets of their fields that we choose) and the (always named) extra expressions that we select. $$\begin{array}{l} - \mt{con} \; \mt{sql\_query} :: \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \\ - \mt{val} \; \mt{sql\_query} : \mt{tables} ::: \{\{\mt{Type}\}\} \\ + \mt{con} \; \mt{sql\_query} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \\ + \mt{val} \; \mt{sql\_query} : \mt{free} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{tables} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedFields} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedExps} ::: \{\mt{Type}\} \\ - \hspace{.1in} \to \{\mt{Rows} : \mt{sql\_query1} \; \mt{tables} \; \mt{selectedFields} \; \mt{selectedExps}, \\ - \hspace{.2in} \mt{OrderBy} : \mt{sql\_order\_by} \; \mt{tables} \; \mt{selectedExps}, \\ + \hspace{.1in} \to [\mt{free} \sim \mt{tables}] \\ + \hspace{.1in} \Rightarrow \{\mt{Rows} : \mt{sql\_query1} \; \mt{free} \; \mt{tables} \; \mt{selectedFields} \; \mt{selectedExps}, \\ + \hspace{.2in} \mt{OrderBy} : \mt{sql\_order\_by} \; (\mt{free} \rc \mt{tables}) \; \mt{selectedExps}, \\ \hspace{.2in} \mt{Limit} : \mt{sql\_limit}, \\ \hspace{.2in} \mt{Offset} : \mt{sql\_offset}\} \\ - \hspace{.1in} \to \mt{sql\_query} \; \mt{selectedFields} \; \mt{selectedExps} + \hspace{.1in} \to \mt{sql\_query} \; \mt{free} \; \mt{selectedFields} \; \mt{selectedExps} \end{array}$$ Queries are used by folding over their results inside transactions. $$\begin{array}{l} - \mt{val} \; \mt{query} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \lambda [\mt{tables} \sim \mt{exps}] \Rightarrow \mt{state} ::: \mt{Type} \to \mt{sql\_query} \; \mt{tables} \; \mt{exps} \\ + \mt{val} \; \mt{query} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \lambda [\mt{tables} \sim \mt{exps}] \Rightarrow \mt{state} ::: \mt{Type} \to \mt{sql\_query} \; [] \; \mt{tables} \; \mt{exps} \\ \hspace{.1in} \to (\$(\mt{exps} \rc \mt{map} \; (\lambda \mt{fields} :: \{\mt{Type}\} \Rightarrow \$\mt{fields}) \; \mt{tables}) \\ \hspace{.2in} \to \mt{state} \to \mt{transaction} \; \mt{state}) \\ \hspace{.1in} \to \mt{state} \to \mt{transaction} \; \mt{state} \end{array}$$ -Most of the complexity of the query encoding is in the type $\mt{sql\_query1}$, which includes simple queries and derived queries based on relational operators. Constructor arguments respectively specify the tables we select from, the subset of fields that we keep from each table for the result rows, and the extra expressions that we select. +Most of the complexity of the query encoding is in the type $\mt{sql\_query1}$, which includes simple queries and derived queries based on relational operators. Constructor arguments respectively specify the free table veriables, the tables we select from, the subset of fields that we keep from each table for the result rows, and the extra expressions that we select. $$\begin{array}{l} - \mt{con} \; \mt{sql\_query1} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \\ + \mt{con} \; \mt{sql\_query1} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \{\mt{Type}\} \to \mt{Type} \\ \\ \mt{type} \; \mt{sql\_relop} \\ \mt{val} \; \mt{sql\_union} : \mt{sql\_relop} \\ @@ -1527,20 +1529,23 @@ $$\begin{array}{l} \end{array}$$ $$\begin{array}{l} - \mt{val} \; \mt{sql\_query1} : \mt{tables} ::: \{\{\mt{Type}\}\} \\ + \mt{val} \; \mt{sql\_query1} : \mt{free} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{tables} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{grouped} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedFields} ::: \{\{\mt{Type}\}\} \\ \hspace{.1in} \to \mt{selectedExps} ::: \{\mt{Type}\} \\ \hspace{.1in} \to \mt{empties} :: \{\mt{Unit}\} \\ - \hspace{.1in} \to [\mt{empties} \sim \mt{selectedFields}] \\ + \hspace{.1in} \to [\mt{free} \sim \mt{tables}] \\ + \hspace{.1in} \Rightarrow [\mt{free} \sim \mt{grouped}] \\ + \hspace{.1in} \Rightarrow [\mt{empties} \sim \mt{selectedFields}] \\ \hspace{.1in} \Rightarrow \{\mt{Distinct} : \mt{bool}, \\ - \hspace{.2in} \mt{From} : \mt{sql\_from\_items} \; \mt{tables}, \\ - \hspace{.2in} \mt{Where} : \mt{sql\_exp} \; \mt{tables} \; [] \; [] \; \mt{bool}, \\ + \hspace{.2in} \mt{From} : \mt{sql\_from\_items} \; \mt{free} \; \mt{tables}, \\ + \hspace{.2in} \mt{Where} : \mt{sql\_exp} \; (\mt{free} \rc \mt{tables}) \; [] \; [] \; \mt{bool}, \\ \hspace{.2in} \mt{GroupBy} : \mt{sql\_subset} \; \mt{tables} \; \mt{grouped}, \\ - \hspace{.2in} \mt{Having} : \mt{sql\_exp} \; \mt{grouped} \; \mt{tables} \; [] \; \mt{bool}, \\ + \hspace{.2in} \mt{Having} : \mt{sql\_exp} \; (\mt{free} \rc \mt{grouped}) \; \mt{tables} \; [] \; \mt{bool}, \\ \hspace{.2in} \mt{SelectFields} : \mt{sql\_subset} \; \mt{grouped} \; (\mt{map} \; (\lambda \_ \Rightarrow []) \; \mt{empties} \rc \mt{selectedFields}), \\ - \hspace{.2in} \mt {SelectExps} : \$(\mt{map} \; (\mt{sql\_exp} \; \mt{grouped} \; \mt{tables} \; []) \; \mt{selectedExps}) \} \\ - \hspace{.1in} \to \mt{sql\_query1} \; \mt{tables} \; \mt{selectedFields} \; \mt{selectedExps} + \hspace{.2in} \mt {SelectExps} : \$(\mt{map} \; (\mt{sql\_exp} \; (\mt{free} \rc \mt{grouped}) \; \mt{tables} \; []) \; \mt{selectedExps}) \} \\ + \hspace{.1in} \to \mt{sql\_query1} \; \mt{free} \; \mt{tables} \; \mt{selectedFields} \; \mt{selectedExps} \end{array}$$ To encode projection of subsets of fields in $\mt{SELECT}$ clauses, and to encode $\mt{GROUP} \; \mt{BY}$ clauses, we rely on a type family $\mt{sql\_subset}$, capturing what it means for one record of table fields to be a subset of another. The main constructor $\mt{sql\_subset}$ ``proves subset facts'' by requiring a split of a record into kept and dropped parts. The extra constructor $\mt{sql\_subset\_all}$ is a convenience for keeping all fields of a record. @@ -1674,17 +1679,27 @@ $$\begin{array}{l} \mt{val} \; \mt{sql\_min} : \mt{t} ::: \mt{Type} \to \mt{sql\_maxable} \; \mt{t} \to \mt{sql\_aggregate} \; \mt{t} \; \mt{t} \end{array}$$ -\texttt{FROM} clauses are specified using a type family. +Any SQL query that returns single columns may be turned into a subquery expression. + +$$\begin{array}{l} +\mt{val} \; \mt{sql\_subquery} : \mt{tables} ::: \{\{\mt{Type}\}\} \to \mt{agg} ::: \{\{\mt{Type}\}\} \to \mt{exps} ::: \{\mt{Type}\} \to \mt{nm} ::: \mt{Name} \to \mt{t} ::: \mt{Type} \\ +\hspace{.1in} \to \mt{sql\_query} \; \mt{tables} \; [] \; [\mt{nm} = \mt{t}] \to \mt{sql\_exp} \; \mt{tables} \; \mt{agg} \; \mt{exps} \; \mt{t} +\end{array}$$ + +\texttt{FROM} clauses are specified using a type family, whose arguments are the free table variables and the table variables bound by this clause. $$\begin{array}{l} - \mt{con} \; \mt{sql\_from\_items} :: \{\{\mt{Type}\}\} \to \mt{Type} \\ - \mt{val} \; \mt{sql\_from\_table} : \mt{t} ::: \mt{Type} \to \mt{fs} ::: \{\mt{Type}\} \to \mt{fieldsOf} \; \mt{t} \; \mt{fs} \to \mt{name} :: \mt{Name} \to \mt{t} \to \mt{sql\_from\_items} \; [\mt{name} = \mt{fs}] \\ - \mt{val} \; \mt{sql\_from\_comma} : \mt{tabs1} ::: \{\{\mt{Type}\}\} \to \mt{tabs2} ::: \{\{\mt{Type}\}\} \to [\mt{tabs1} \sim \mt{tabs2}] \\ - \hspace{.1in} \Rightarrow \mt{sql\_from\_items} \; \mt{tabs1} \to \mt{sql\_from\_items} \; \mt{tabs2} \\ - \hspace{.1in} \to \mt{sql\_from\_items} \; (\mt{tabs1} \rc \mt{tabs2}) \\ - \mt{val} \; \mt{sql\_inner\_join} : \mt{tabs1} ::: \{\{\mt{Type}\}\} \to \mt{tabs2} ::: \{\{\mt{Type}\}\} \to [\mt{tabs1} \sim \mt{tabs2}] \\ - \hspace{.1in} \Rightarrow \mt{sql\_from\_items} \; \mt{tabs1} \to \mt{sql\_from\_items} \; \mt{tabs2} \\ - \hspace{.1in} \to \mt{sql\_exp} \; (\mt{tabs1} \rc \mt{tabs2}) \; [] \; [] \; \mt{bool} \\ - \hspace{.1in} \to \mt{sql\_from\_items} \; (\mt{tabs1} \rc \mt{tabs2}) + \mt{con} \; \mt{sql\_from\_items} :: \{\{\mt{Type}\}\} \to \{\{\mt{Type}\}\} \to \mt{Type} \\ + \mt{val} \; \mt{sql\_from\_table} : \mt{free} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to \mt{t} ::: \mt{Type} \to \mt{fs} ::: \{\mt{Type}\} \to \mt{fieldsOf} \; \mt{t} \; \mt{fs} \to \mt{name} :: \mt{Name} \to \mt{t} \to \mt{sql\_from\_items} \; \mt{free} \; [\mt{name} = \mt{fs}] \\ + \mt{val} \; \mt{sql\_from\_query} : \mt{free} ::: \{\{\mt{Type}\}\} \to \mt{fs} ::: \{\mt{Type}\} \to \mt{name} :: \mt{Name} \to \mt{sql\_query} \; \mt{free} \; [] \; \mt{fs} \to \mt{sql\_from\_items} \; \mt{free} \; [\mt{name} = \mt{fs}] \\ + \mt{val} \; \mt{sql\_from\_comma} : \mt{free} ::: \mt{tabs1} ::: \{\{\mt{Type}\}\} \to \mt{tabs2} ::: \{\{\mt{Type}\}\} \to [\mt{tabs1} \sim \mt{tabs2}] \\ + \hspace{.1in} \Rightarrow \mt{sql\_from\_items} \; \mt{free} \; \mt{tabs1} \to \mt{sql\_from\_items} \; \mt{free} \; \mt{tabs2} \\ + \hspace{.1in} \to \mt{sql\_from\_items} \; \mt{free} \; (\mt{tabs1} \rc \mt{tabs2}) \\ + \mt{val} \; \mt{sql\_inner\_join} : \mt{free} ::: \{\{\mt{Type}\}\} \to \mt{tabs1} ::: \{\{\mt{Type}\}\} \to \mt{tabs2} ::: \{\{\mt{Type}\}\} \\ + \hspace{.1in} \to [\mt{free} \sim \mt{tabs1}] \Rightarrow [\mt{free} \sim \mt{tabs2}] \Rightarrow [\mt{tabs1} \sim \mt{tabs2}] \\ + \hspace{.1in} \Rightarrow \mt{sql\_from\_items} \; \mt{free} \; \mt{tabs1} \to \mt{sql\_from\_items} \; \mt{free} \; \mt{tabs2} \\ + \hspace{.1in} \to \mt{sql\_exp} \; (\mt{free} \rc \mt{tabs1} \rc \mt{tabs2}) \; [] \; [] \; \mt{bool} \\ + \hspace{.1in} \to \mt{sql\_from\_items} \; \mt{free} \; (\mt{tabs1} \rc \mt{tabs2}) \end{array}$$ Besides these basic cases, outer joins are supported, which requires a type class for turning non-$\mt{option}$ columns into $\mt{option}$ columns. @@ -1697,11 +1712,12 @@ $$\begin{array}{l} Left, right, and full outer joins can now be expressed using functions that accept records of $\mt{nullify}$ instances. Here, we give only the type for a left join as an example. $$\begin{array}{l} - \mt{val} \; \mt{sql\_left\_join} : \mt{tabs1} ::: \{\{\mt{Type}\}\} \to \mt{tabs2} ::: \{\{(\mt{Type} \times \mt{Type})\}\} \to [\mt{tabs1} \sim \mt{tabs2}] \\ + \mt{val} \; \mt{sql\_left\_join} : \mt{free} ::: \{\{\mt{Type}\}\} \to \mt{tabs1} ::: \{\{\mt{Type}\}\} \to \mt{tabs2} ::: \{\{(\mt{Type} \times \mt{Type})\}\} \\ + \hspace{.1in} \to [\mt{free} \sim \mt{tabs1}] \Rightarrow [\mt{free} \sim \mt{tabs2}] \Rightarrow [\mt{tabs1} \sim \mt{tabs2}] \\ \hspace{.1in} \Rightarrow \$(\mt{map} \; (\lambda \mt{r} \Rightarrow \$(\mt{map} \; (\lambda \mt{p} :: (\mt{Type} \times \mt{Type}) \Rightarrow \mt{nullify} \; \mt{p}.1 \; \mt{p}.2) \; \mt{r})) \; \mt{tabs2}) \\ - \hspace{.1in} \to \mt{sql\_from\_items} \; \mt{tabs1} \to \mt{sql\_from\_items} \; (\mt{map} \; (\mt{map} \; (\lambda \mt{p} :: (\mt{Type} \times \mt{Type}) \Rightarrow \mt{p}.1)) \; \mt{tabs2}) \\ - \hspace{.1in} \to \mt{sql\_exp} \; (\mt{tabs1} \rc \mt{map} \; (\mt{map} \; (\lambda \mt{p} :: (\mt{Type} \times \mt{Type}) \Rightarrow \mt{p}.1)) \; \mt{tabs2}) \; [] \; [] \; \mt{bool} \\ - \hspace{.1in} \to \mt{sql\_from\_items} \; (\mt{tabs1} \rc \mt{map} \; (\mt{map} \; (\lambda \mt{p} :: (\mt{Type} \times \mt{Type}) \Rightarrow \mt{p}.2)) \; \mt{tabs2}) + \hspace{.1in} \to \mt{sql\_from\_items} \; \mt{free} \; \mt{tabs1} \to \mt{sql\_from\_items} \; \mt{free} \; (\mt{map} \; (\mt{map} \; (\lambda \mt{p} :: (\mt{Type} \times \mt{Type}) \Rightarrow \mt{p}.1)) \; \mt{tabs2}) \\ + \hspace{.1in} \to \mt{sql\_exp} \; (\mt{free} \rc \mt{tabs1} \rc \mt{map} \; (\mt{map} \; (\lambda \mt{p} :: (\mt{Type} \times \mt{Type}) \Rightarrow \mt{p}.1)) \; \mt{tabs2}) \; [] \; [] \; \mt{bool} \\ + \hspace{.1in} \to \mt{sql\_from\_items} \; \mt{free} \; (\mt{tabs1} \rc \mt{map} \; (\mt{map} \; (\lambda \mt{p} :: (\mt{Type} \times \mt{Type}) \Rightarrow \mt{p}.2)) \; \mt{tabs2}) \end{array}$$ We wrap up the definition of query syntax with the types used in representing $\mt{ORDER} \; \mt{BY}$, $\mt{LIMIT}$, and $\mt{OFFSET}$ clauses. @@ -1953,6 +1969,7 @@ $$\begin{array}{rrcll} &&& \{\{e\}\} \; \mt{AS} \; t & \textrm{computed table expression, with local name} \\ \textrm{$\mt{FROM}$ items} & F &::=& T \mid \{\{e\}\} \mid F \; J \; \mt{JOIN} \; F \; \mt{ON} \; E \\ &&& \mid F \; \mt{CROSS} \; \mt{JOIN} \ F \\ + &&& \mid (Q) \; \mt{AS} \; t \\ \textrm{Joins} & J &::=& [\mt{INNER}] \\ &&& \mid [\mt{LEFT} \mid \mt{RIGHT} \mid \mt{FULL}] \; [\mt{OUTER}] \\ \textrm{SQL expressions} & E &::=& p & \textrm{column references} \\ @@ -1968,6 +1985,7 @@ $$\begin{array}{rrcll} &&& E \; b \; E & \textrm{binary operators} \\ &&& \mt{COUNT}(\ast) & \textrm{count number of rows} \\ &&& a(E) & \textrm{other aggregate function} \\ + &&& (Q) & \textrm{subquery (must return a single expression column)} \\ &&& (E) & \textrm{explicit precedence} \\ \textrm{Nullary operators} & n &::=& \mt{CURRENT\_TIMESTAMP} \\ \textrm{Unary operators} & u &::=& \mt{NOT} \\ -- cgit v1.2.3 From ff8b80dbb7cc9e5b8a48efbbf6f2f1009e1ab68e Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 25 Mar 2010 16:41:51 -0400 Subject: 'AS' clauses for expression columns may be omitted --- doc/manual.tex | 3 +++ src/urweb.grm | 17 ++++++++++------- tests/agg.ur | 17 +++++++++-------- 3 files changed, 22 insertions(+), 15 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index eea0a01a..86fc4843 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1959,6 +1959,7 @@ $$\begin{array}{rrcll} &&& p,^+ & \textrm{particular columns} \\ \textrm{Pre-projections} & p &::=& t.f & \textrm{one column from a table} \\ &&& t.\{\{c\}\} & \textrm{a record of columns from a table (of kind $\{\mt{Type}\}$)} \\ + &&& E \; [\mt{AS} \; f] & \textrm{expression column} \\ \textrm{Table names} & t &::=& x & \textrm{constant table name (automatically capitalized)} \\ &&& X & \textrm{constant table name} \\ &&& \{\{c\}\} & \textrm{computed table name (of kind $\mt{Name}$)} \\ @@ -1997,6 +1998,8 @@ $$\begin{array}{rrcll} Additionally, an SQL expression may be inserted into normal Ur code with the syntax $(\mt{SQL} \; E)$ or $(\mt{WHERE} \; E)$. Similar shorthands exist for other nonterminals, with the prefix $\mt{FROM}$ for $\mt{FROM}$ items and $\mt{SELECT1}$ for pre-queries. +Unnamed expression columns in $\mt{SELECT}$ clauses are assigned consecutive natural numbers, starting with 1. + \subsubsection{DML} DML commands $D$ are added to the rules for expressions $e$. diff --git a/src/urweb.grm b/src/urweb.grm index 995d4664..ad3de6b2 100644 --- a/src/urweb.grm +++ b/src/urweb.grm @@ -42,7 +42,7 @@ fun entable t = datatype select_item = Field of con * con - | Exp of con * exp + | Exp of con option * exp | Fields of con * con datatype select = @@ -58,7 +58,7 @@ fun eqTnames ((c1, _), (c2, _)) = | (CName x1, CName x2) => x1 = x2 | _ => false -fun amend_select loc (si, (tabs, exps)) = +fun amend_select loc (si, (count, tabs, exps)) = case si of Field (tx, fx) => let @@ -76,7 +76,7 @@ fun amend_select loc (si, (tabs, exps)) = else ErrorMsg.errorAt loc "Select of field from unbound table"; - (tabs, exps) + (count, tabs, exps) end | Fields (tx, fs) => let @@ -92,9 +92,10 @@ fun amend_select loc (si, (tabs, exps)) = else ErrorMsg.errorAt loc "Select of field from unbound table"; - (tabs, exps) + (count, tabs, exps) end - | Exp (c, e) => (tabs, (c, e) :: exps) + | Exp (SOME c, e) => (count, tabs, (c, e) :: exps) + | Exp (NONE, e) => (count+1, tabs, ((CName (Int.toString count), loc), e) :: exps) fun amend_group loc (gi, tabs) = let @@ -1460,7 +1461,8 @@ query1 : SELECT dopt select FROM tables wopt gopt hopt | Items sis => let val tabs = map (fn nm => (nm, (CRecord [], loc))) (#1 tables) - val (tabs, exps) = foldl (amend_select loc) (tabs, []) sis + val (_, tabs, exps) = foldl (amend_select loc) + (1, tabs, []) sis val empties = List.mapPartial (fn (nm, (CRecord [], _)) => SOME nm | _ => NONE) tabs @@ -1662,7 +1664,8 @@ fident : CSYMBOL (CName CSYMBOL, s (CSYMBOLleft, CSYMBOLr | LBRACE cexp RBRACE (cexp) seli : tident DOT fident (Field (tident, fident)) - | sqlexp AS fident (Exp (fident, sqlexp)) + | sqlexp (Exp (NONE, sqlexp)) + | sqlexp AS fident (Exp (SOME fident, sqlexp)) | tident DOT LBRACE LBRACE cexp RBRACE RBRACE (Fields (tident, cexp)) selis : seli ([seli]) diff --git a/tests/agg.ur b/tests/agg.ur index 55e22c28..19a8644b 100644 --- a/tests/agg.ur +++ b/tests/agg.ur @@ -1,13 +1,14 @@ table t1 : {A : int, B : string, C : float} table t2 : {A : float, D : int, E : option string} -val q1 = (SELECT COUNT( * ) AS X FROM t1) -val q2 = (SELECT AVG(t1.A) AS X FROM t1) -val q3 = (SELECT SUM(t1.C) AS X FROM t1) -val q4 = (SELECT MIN(t1.B) AS X, MAX(t1.A) AS Y FROM t1) -val q5 = (SELECT SUM(t1.A) AS X FROM t1 GROUP BY t1.B) -val q6 = (SELECT COUNT(t2.E) AS N FROM t2 GROUP BY t2.D) +val q1 : sql_query [] _ _ = (SELECT COUNT( * ) FROM t1) +val q2 : sql_query [] _ _ = (SELECT AVG(t1.A) FROM t1) +val q3 : sql_query [] _ _ = (SELECT SUM(t1.C) FROM t1) +val q4 : sql_query [] _ _ = (SELECT MIN(t1.B), MAX(t1.A) FROM t1) +val q5 : sql_query [] _ _ = (SELECT SUM(t1.A) FROM t1 GROUP BY t1.B) +val q6 = (SELECT COUNT(t2.E) FROM t2 GROUP BY t2.D) fun main () : transaction page = - xml <- queryX q6 (fn r => {[r.N]};); - return {xml} + xml <- queryX q6 (fn r => {[r.1]};); + xml2 <- queryX q4 (fn r => {[r.1]}, {[r.2]};); + return {xml}
{xml2}
-- cgit v1.2.3 From 5c786a7034aa1e2e2f27062a2befa738492ea601 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 17 Apr 2010 13:57:10 -0400 Subject: Forward reference to URI convention --- doc/manual.tex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 86fc4843..4d8ff987 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -166,7 +166,7 @@ To compile project \texttt{P.urp}, simply run \begin{verbatim} urweb P \end{verbatim} -The output executable is a standalone web server. Run it with the command-line argument \texttt{-h} to see which options it takes. If the project file lists a database, the web server will attempt to connect to that database on startup. +The output executable is a standalone web server. Run it with the command-line argument \texttt{-h} to see which options it takes. If the project file lists a database, the web server will attempt to connect to that database on startup. See Section \ref{structure} for an explanation of the URI mapping convention, which determines how each page of your application may be accessed via URLs. To time how long the different compiler phases run, without generating an executable, run \begin{verbatim} @@ -2032,7 +2032,7 @@ $$\begin{array}{rrcll} \end{array}$$ -\section{The Structure of Web Applications} +\section{\label{structure}The Structure of Web Applications} A web application is built from a series of modules, with one module, the last one appearing in the \texttt{.urp} file, designated as the main module. The signature of the main module determines the URL entry points to the application. Such an entry point should have type $\mt{t1} \to \ldots \to \mt{tn} \to \mt{transaction} \; \mt{page}$, for any integer $n \geq 0$, where $\mt{page}$ is a type synonym for top-level HTML pages, defined in $\mt{Basis}$. If such a function is at the top level of main module $M$, with $n = 0$, it will be accessible at URI \texttt{/M/f}, and so on for more deeply-nested functions, as described in Section \ref{tag} below. Arguments to an entry-point function are deserialized from the part of the URI following \texttt{f}. -- cgit v1.2.3 From df1314f40a89b39188c26a303f09e673bf061070 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Tue, 7 Sep 2010 09:47:06 -0400 Subject: Updating documentation --- CHANGELOG | 5 +++++ doc/manual.tex | 24 +++++++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index 3d3506f6..943d7dff 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,12 @@ Next ======== - Polymorphic variants (see Basis.variant) +- New 'onError' directive for .urp files - (* *) and comments in XML +- Basis.classes, Basis.confirm, and Basis.tryDml +- Invocations like 'urweb foo' will compile foo.ur as a single-file project, + even if no foo.urp exists +- Bug fixes and optimization improvements ======== 20100603 diff --git a/doc/manual.tex b/doc/manual.tex index 4d8ff987..fa0f0fcd 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -147,6 +147,7 @@ Here is the complete list of directive forms. ``FFI'' stands for ``foreign func \item \texttt{jsFunc Module.ident=name} gives the JavaScript name of an FFI value. \item \texttt{library FILENAME} parses \texttt{FILENAME.urp} and merges its contents with the rest of the current file's contents. If \texttt{FILENAME.urp} doesn't exist, the compiler also tries \texttt{FILENAME/lib.urp}. \item \texttt{link FILENAME} adds \texttt{FILENAME} to the list of files to be passed to the GCC linker at the end of compilation. This is most useful for importing extra libraries needed by new FFI modules. +\item \texttt{onError Module.var} changes the handling of fatal application errors. Instead of displaying a default, ugly error 500 page, the error page will be generated by calling function \texttt{Module.var} on a piece of XML representing the error message. The error handler should have type $\mt{xbody} \to \mt{transaction} \; \mt{page}$. Note that the error handler \emph{cannot} be in the application's main module, since that would register it as explicitly callable via URLs. \item \texttt{path NAME=VALUE} creates a mapping from \texttt{NAME} to \texttt{VALUE}. This mapping may be used at the beginnings of filesystem paths given to various other configuration directives. A path like \texttt{\$NAME/rest} is expanded to \texttt{VALUE/rest}. There is an initial mapping from the empty name (for paths like \texttt{\$/list}) to the directory where the Ur/Web standard library is installed. If you accept the default \texttt{configure} options, this directory is \texttt{/usr/local/lib/urweb/ur}. \item \texttt{prefix PREFIX} sets the prefix included before every URI within the generated application. The default is \texttt{/}. \item \texttt{profile} generates an executable that may be used with gprof. @@ -278,6 +279,7 @@ fastcgi.server = ( \item \texttt{-static}: Link the runtime system statically. The default is to link against dynamic libraries. \end{itemize} +There is an additional convenience method for invoking \texttt{urweb}. If the main argument is \texttt{FOO}, and \texttt{FOO.ur} exists but \texttt{FOO.urp} doesn't, then the invocation is interpreted as if called on a \texttt{.urp} file containing \texttt{FOO} as its only main entry, with an additional \texttt{rewrite all FOO/*} directive. \section{Ur Syntax} @@ -311,6 +313,8 @@ We write $\ell$ for literals of the primitive types, for the most part following This version of the manual doesn't include operator precedences; see \texttt{src/urweb.grm} for that. +As in the ML language family, the syntax \texttt{(* ... *)} is used for (nestable) comments. Within XML literals, Ur/Web also supports the usual \texttt{} XML comments. + \subsection{\label{core}Core Syntax} \emph{Kinds} classify types and other compile-time-only entities. Each kind in the grammar is listed with a description of the sort of data it classifies. @@ -1283,6 +1287,13 @@ $$\begin{array}{l} The only unusual element of this list is the $\mt{blob}$ type, which stands for binary sequences. Simple blobs can be created from strings via $\mt{Basis.textBlob}$. Blobs will also be generated from HTTP file uploads. +Ur also supports \emph{polymorphic variants}, a dual to extensible records that has been popularized by OCaml. A type $\mt{variant} \; r$ represents an $n$-ary sum type, with one constructor for each field of record $r$. Each constructor $c$ takes an argument of type $r.c$; the type $\{\}$ can be used to ``simulate'' a nullary constructor. The \cd{make} function builds a variant value, while \cd{match} implements pattern-matching, with match cases represented as records of functions. +$$\begin{array}{l} + \mt{con} \; \mt{variant} :: \{\mt{Type}\} \to \mt{Type} \\ + \mt{val} \; \mt{make} : \mt{nm} :: \mt{Name} \to \mt{t} ::: \mt{Type} \to \mt{ts} ::: \{\mt{Type}\} \to [[\mt{nm}] \sim \mt{ts}] \Rightarrow \mt{t} \to \mt{variant} \; ([\mt{nm} = \mt{t}] \rc \mt{ts}) \\ + \mt{val} \; \mt{match} : \mt{ts} ::: \{\mt{Type}\} \to \mt{t} ::: \mt{Type} \to \mt{variant} \; \mt{ts} \to \$(\mt{map} \; (\lambda \mt{t'} \Rightarrow \mt{t'} \to \mt{t}) \; \mt{ts}) \to \mt{t} +\end{array}$$ + Another important generic Ur element comes at the beginning of \texttt{top.urs}. $$\begin{array}{l} @@ -1750,6 +1761,12 @@ $$\begin{array}{l} \mt{val} \; \mt{dml} : \mt{dml} \to \mt{transaction} \; \mt{unit} \end{array}$$ +The function $\mt{Basis.dml}$ will trigger a fatal application error if the command fails, for instance, because a data integrity constraint is violated. An alternate function returns an error message as a string instead. + +$$\begin{array}{l} + \mt{val} \; \mt{tryDml} : \mt{dml} \to \mt{transaction} \; (\mt{option} \; \mt{string}) +\end{array}$$ + Properly-typed records may be used to form $\mt{INSERT}$ commands. $$\begin{array}{l} \mt{val} \; \mt{insert} : \mt{fields} ::: \{\mt{Type}\} \to \mt{sql\_table} \; \mt{fields} \\ @@ -1808,7 +1825,7 @@ $$\begin{array}{l} \hspace{.1in} \to \mt{tag} \; (\mt{attrsGiven} \rc \mt{attrsAbsent}) \; \mt{ctxOuter} \; \mt{ctxInner} \; \mt{useOuter} \; \mt{bindOuter} \\ \hspace{.1in} \to \mt{xml} \; \mt{ctxInner} \; \mt{useInner} \; \mt{bindInner} \to \mt{xml} \; \mt{ctxOuter} \; (\mt{useOuter} \rc \mt{useInner}) \; (\mt{bindOuter} \rc \mt{bindInner}) \end{array}$$ -Note that any tag may be assigned a CSS class. This is the sole way of making use of the values produced by $\mt{style}$ declarations. Ur/Web itself doesn't deal with the syntax or semantics of style sheets; they can be linked via URLs with \texttt{link} tags. However, Ur/Web does make it easy to calculate upper bounds on usage of CSS classes through program analysis. +Note that any tag may be assigned a CSS class. This is the sole way of making use of the values produced by $\mt{style}$ declarations. Ur/Web itself doesn't deal with the syntax or semantics of style sheets; they can be linked via URLs with \texttt{link} tags. However, Ur/Web does make it easy to calculate upper bounds on usage of CSS classes through program analysis. The function $\mt{Basis.classes}$ can be used to specify a list of CSS classes for a single tag. Two XML fragments may be concatenated. $$\begin{array}{l} @@ -1837,9 +1854,10 @@ Ur/Web supports running code on web browsers, via automatic compilation to JavaS \subsubsection{The Basics} -Clients can open alert dialog boxes, in the usual annoying JavaScript way. +Clients can open alert and confirm dialog boxes, in the usual annoying JavaScript way. $$\begin{array}{l} - \mt{val} \; \mt{alert} : \mt{string} \to \mt{transaction} \; \mt{unit} + \mt{val} \; \mt{alert} : \mt{string} \to \mt{transaction} \; \mt{unit} \\ + \mt{val} \; \mt{confirm} : \mt{string} \to \mt{transaction} \; \mt{bool} \end{array}$$ Any transaction may be run in a new thread with the $\mt{spawn}$ function. -- cgit v1.2.3 From dfe1d4fdd1640b07a05db176d9dc89fcbb124edf Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 30 Sep 2010 18:12:04 -0400 Subject: Mention empty-third-field 'rewrite' directives --- doc/manual.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index fa0f0fcd..98b3b63c 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -151,7 +151,7 @@ Here is the complete list of directive forms. ``FFI'' stands for ``foreign func \item \texttt{path NAME=VALUE} creates a mapping from \texttt{NAME} to \texttt{VALUE}. This mapping may be used at the beginnings of filesystem paths given to various other configuration directives. A path like \texttt{\$NAME/rest} is expanded to \texttt{VALUE/rest}. There is an initial mapping from the empty name (for paths like \texttt{\$/list}) to the directory where the Ur/Web standard library is installed. If you accept the default \texttt{configure} options, this directory is \texttt{/usr/local/lib/urweb/ur}. \item \texttt{prefix PREFIX} sets the prefix included before every URI within the generated application. The default is \texttt{/}. \item \texttt{profile} generates an executable that may be used with gprof. -\item \texttt{rewrite KIND FROM TO} gives a rule for rewriting canonical module paths. For instance, the canonical path of a page may be \texttt{Mod1.Mod2.mypage}, while you would rather the page were accessed via a URL containing only \texttt{page}. The directive \texttt{rewrite url Mod1/Mod2/mypage page} would accomplish that. The possible values of \texttt{KIND} determine which kinds of objects are affected. The kind \texttt{all} matches any object, and \texttt{url} matches page URLs. The kinds \texttt{table}, \texttt{sequence}, and \texttt{view} match those sorts of SQL entities, and \texttt{relation} matches any of those three. \texttt{cookie} matches HTTP cookies, and \texttt{style} matches CSS class names. If \texttt{FROM} ends in \texttt{/*}, it is interpreted as a prefix matching rule, and rewriting occurs by replacing only the appropriate prefix of a path with \texttt{TO}. While the actual external names of relations and styles have parts separated by underscores instead of slashes, all rewrite rules must be written in terms of slashes. +\item \texttt{rewrite KIND FROM TO} gives a rule for rewriting canonical module paths. For instance, the canonical path of a page may be \texttt{Mod1.Mod2.mypage}, while you would rather the page were accessed via a URL containing only \texttt{page}. The directive \texttt{rewrite url Mod1/Mod2/mypage page} would accomplish that. The possible values of \texttt{KIND} determine which kinds of objects are affected. The kind \texttt{all} matches any object, and \texttt{url} matches page URLs. The kinds \texttt{table}, \texttt{sequence}, and \texttt{view} match those sorts of SQL entities, and \texttt{relation} matches any of those three. \texttt{cookie} matches HTTP cookies, and \texttt{style} matches CSS class names. If \texttt{FROM} ends in \texttt{/*}, it is interpreted as a prefix matching rule, and rewriting occurs by replacing only the appropriate prefix of a path with \texttt{TO}. The \texttt{TO} field may be left empty to express the idea of deleting a prefix. For instance, \texttt{rewrite url Main/*} will strip all \texttt{Main/} prefixes from URLs. While the actual external names of relations and styles have parts separated by underscores instead of slashes, all rewrite rules must be written in terms of slashes. \item \texttt{safeGet URI} asks to allow the page handler assigned this canonical URI prefix to cause persistent side effects, even if accessed via an HTTP \cd{GET} request. \item \texttt{script URL} adds \texttt{URL} to the list of extra JavaScript files to be included at the beginning of any page that uses JavaScript. This is most useful for importing JavaScript versions of functions found in new FFI modules. \item \texttt{serverOnly Module.ident} registers an FFI function or transaction that may only be run on the server. -- cgit v1.2.3 From bfeac162a328dba937a28e747e4fc4006fac500c Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 10 Oct 2010 13:07:38 -0400 Subject: Flex kinds for type-level tuples; ::_ notation --- demo/batchFun.ur | 22 ++++++++-------------- demo/crud.ur | 46 ++++++++++++++++++---------------------------- demo/metaform.ur | 6 +++--- doc/manual.tex | 2 ++ src/elab.sml | 1 + src/elab_print.sml | 10 ++++++++++ src/elab_util.sml | 12 +++++++++++- src/elaborate.sml | 49 ++++++++++++++++++++++++++++++++++++++++--------- src/explify.sml | 2 ++ src/urweb.grm | 27 ++++++++++++++++++++++++++- src/urweb.lex | 1 + tests/ktuple.ur | 2 ++ tests/ktuple.urp | 1 + 13 files changed, 125 insertions(+), 56 deletions(-) create mode 100644 tests/ktuple.ur create mode 100644 tests/ktuple.urp (limited to 'doc') diff --git a/demo/batchFun.ur b/demo/batchFun.ur index f665b132..ca48c7dc 100644 --- a/demo/batchFun.ur +++ b/demo/batchFun.ur @@ -6,7 +6,7 @@ con colMeta = fn (db :: Type, state :: Type) => NewState : transaction state, Widget : state -> xbody, ReadState : state -> transaction db} -con colsMeta = fn cols :: {(Type * Type)} => $(map colMeta cols) +con colsMeta = fn cols => $(map colMeta cols) fun default [t] (sh : show t) (rd : read t) (inj : sql_injectable t) name : colMeta (t, source string) = @@ -46,10 +46,8 @@ functor Make(M : sig fun add r = dml (insert t (@foldR2 [fst] [colMeta] - [fn cols => $(map (fn t :: (Type * Type) => - sql_exp [] [] [] t.1) cols)] - (fn [nm :: Name] [t :: (Type * Type)] [rest :: {(Type * Type)}] - [[nm] ~ rest] input col acc => + [fn cols => $(map (fn t => sql_exp [] [] [] t.1) cols)] + (fn [nm :: Name] [t ::_] [rest ::_] [[nm] ~ rest] input col acc => acc ++ {nm = @sql_inject col.Inject input}) {} M.fl (r -- #Id) M.cols ++ {Id = (SQL {[r.Id]})})) @@ -73,8 +71,7 @@ functor Make(M : sig {[r.Id]} {@mapX2 [colMeta] [fst] [_] - (fn [nm :: Name] [p :: (Type * Type)] [rest :: {(Type * Type)}] - [[nm] ~ rest] m v => + (fn [nm :: Name] [p ::_] [rest ::_] [[nm] ~ rest] m v => {m.Show v}) M.fl M.cols (r -- #Id)} {if withDel then @@ -89,8 +86,7 @@ functor Make(M : sig Id {@mapX [colMeta] [_] - (fn [nm :: Name] [p :: (Type * Type)] [rest :: {(Type * Type)}] - [[nm] ~ rest] m => + (fn [nm :: Name] [p ::_] [rest ::_] [[nm] ~ rest] m => {[m.Nam]}) M.fl M.cols} @@ -104,7 +100,7 @@ functor Make(M : sig id <- source ""; inps <- @foldR [colMeta] [fn r => transaction ($(map snd r))] - (fn [nm :: Name] [p :: (Type * Type)] [rest :: {(Type * Type)}] [[nm] ~ rest] m acc => + (fn [nm :: Name] [p ::_] [rest ::_] [[nm] ~ rest] m acc => s <- m.NewState; r <- acc; return ({nm = s} ++ r)) @@ -115,8 +111,7 @@ functor Make(M : sig fun add () = id <- get id; vs <- @foldR2 [colMeta] [snd] [fn r => transaction ($(map fst r))] - (fn [nm :: Name] [p :: (Type * Type)] [rest :: {(Type * Type)}] - [[nm] ~ rest] m s acc => + (fn [nm :: Name] [p ::_] [rest ::_] [[nm] ~ rest] m s acc => v <- m.ReadState s; r <- acc; return ({nm = v} ++ r)) @@ -145,8 +140,7 @@ functor Make(M : sig {@mapX2 [colMeta] [snd] [_] - (fn [nm :: Name] [p :: (Type * Type)] [rest :: {(Type * Type)}] - [[nm] ~ rest] m s => + (fn [nm :: Name] [p ::_] [rest ::_] [[nm] ~ rest] m s => ) M.fl M.cols inps} diff --git a/demo/crud.ur b/demo/crud.ur index 82739772..2fc82c1b 100644 --- a/demo/crud.ur +++ b/demo/crud.ur @@ -5,7 +5,7 @@ con colMeta = fn (db :: Type, widget :: Type) => WidgetPopulated : nm :: Name -> db -> xml form [] [nm = widget], Parse : widget -> db, Inject : sql_injectable db} -con colsMeta = fn cols :: {(Type * Type)} => $(map colMeta cols) +con colsMeta = fn cols => $(map colMeta cols) fun default [t] (sh : show t) (rd : read t) (inj : sql_injectable t) name : colMeta (t, string) = @@ -51,10 +51,9 @@ functor Make(M : sig {@mapX2 [fst] [colMeta] [tr] - (fn [nm :: Name] [t :: (Type * Type)] [rest :: {(Type * Type)}] - [[nm] ~ rest] v col => - - ) + (fn [nm :: Name] [t ::_] [rest ::_] [[nm] ~ rest] v col => + + ) M.fl (fs.T -- #Id) M.cols} {@mapX [colMeta] [tr] - (fn [nm :: Name] [t :: (Type * Type)] [rest :: {(Type * Type)}] - [[nm] ~ rest] col => - - ) + (fn [nm :: Name] [t ::_] [rest ::_] [[nm] ~ rest] col => + + ) M.fl M.cols} {rows} @@ -79,12 +77,11 @@ functor Make(M : sig


- {@foldR [colMeta] [fn cols :: {(Type * Type)} => xml form [] (map snd cols)] - (fn [nm :: Name] [t :: (Type * Type)] [rest :: {(Type * Type)}] - [[nm] ~ rest] (col : colMeta t) (acc : xml form [] (map snd rest)) => -
  • {cdata col.Nam}: {col.Widget [nm]}
  • - {useMore acc} -
    ) + {@foldR [colMeta] [fn cols => xml form [] (map snd cols)] + (fn [nm :: Name] [t ::_] [rest ::_] [[nm] ~ rest] (col : colMeta t) (acc : xml form [] (map snd rest)) => +
  • {cdata col.Nam}: {col.Widget [nm]}
  • + {useMore acc} +
    ) M.fl M.cols} @@ -96,10 +93,8 @@ functor Make(M : sig id <- nextval seq; dml (insert tab (@foldR2 [snd] [colMeta] - [fn cols => $(map (fn t :: (Type * Type) => - sql_exp [] [] [] t.1) cols)] - (fn [nm :: Name] [t :: (Type * Type)] [rest :: {(Type * Type)}] - [[nm] ~ rest] => + [fn cols => $(map (fn t => sql_exp [] [] [] t.1) cols)] + (fn [nm :: Name] [t ::_] [rest ::_] [[nm] ~ rest] => fn input col acc => acc ++ {nm = @sql_inject col.Inject (col.Parse input)}) {} M.fl inputs M.cols ++ {Id = (SQL {[id]})})); @@ -115,12 +110,8 @@ functor Make(M : sig fun save (inputs : $(map snd M.cols)) = dml (update [map fst M.cols] (@foldR2 [snd] [colMeta] - [fn cols => $(map (fn t :: (Type * Type) => - sql_exp [T = [Id = int] - ++ map fst M.cols] - [] [] t.1) cols)] - (fn [nm :: Name] [t :: (Type * Type)] [rest :: {(Type * Type)}] - [[nm] ~ rest] => + [fn cols => $(map (fn t => sql_exp [T = [Id = int] ++ map fst M.cols] [] [] t.1) cols)] + (fn [nm :: Name] [t ::_] [rest ::_] [[nm] ~ rest] => fn input col acc => acc ++ {nm = @sql_inject col.Inject (col.Parse input)}) {} M.fl inputs M.cols) @@ -136,9 +127,8 @@ functor Make(M : sig case fso : (Basis.option {Tab : $(map fst M.cols)}) of None => return Not found! | Some fs => return - {@foldR2 [fst] [colMeta] [fn cols :: {(Type * Type)} => xml form [] (map snd cols)] - (fn [nm :: Name] [t :: (Type * Type)] [rest :: {(Type * Type)}] - [[nm] ~ rest] (v : t.1) (col : colMeta t) + {@foldR2 [fst] [colMeta] [fn cols => xml form [] (map snd cols)] + (fn [nm :: Name] [t ::_] [rest ::_] [[nm] ~ rest] (v : t.1) (col : colMeta t) (acc : xml form [] (map snd rest)) =>
  • {cdata col.Nam}: {col.WidgetPopulated [nm] v}
  • diff --git a/demo/metaform.ur b/demo/metaform.ur index 0a664005..729b7d08 100644 --- a/demo/metaform.ur +++ b/demo/metaform.ur @@ -6,7 +6,7 @@ functor Make (M : sig fun handler values = return {@mapUX2 [string] [string] [body] - (fn [nm :: Name] [rest :: {Unit}] [[nm] ~ rest] name value => + (fn [nm :: Name] [rest ::_] [[nm] ~ rest] name value =>
  • {[name]} = {[value]}
  • ) M.fl M.names values} @@ -14,8 +14,8 @@ functor Make (M : sig fun main () = return - {@foldUR [string] [fn cols :: {Unit} => xml form [] (mapU string cols)] - (fn [nm :: Name] [rest :: {Unit}] [[nm] ~ rest] name + {@foldUR [string] [fn cols => xml form [] (mapU string cols)] + (fn [nm :: Name] [rest ::_] [[nm] ~ rest] name (acc : xml form [] (mapU string rest)) =>
  • {[name]}:
  • {useMore acc} diff --git a/doc/manual.tex b/doc/manual.tex index 98b3b63c..9dbdb505 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -495,6 +495,8 @@ A tuple type $(\tau_1, \ldots, \tau_n)$ expands to a record type $\{1 = \tau_1, In general, several adjacent $\lambda$ forms may be combined into one, and kind and type annotations may be omitted, in which case they are implicitly included as wildcards. More formally, for constructor-level abstractions, we can define a new non-terminal $b ::= x \mid (x :: \kappa) \mid X$ and allow composite abstractions of the form $\lambda b^+ \Rightarrow c$, elaborating into the obvious sequence of one core $\lambda$ per element of $b^+$. +In some contexts, the parser isn't happy with token sequences like $x :: \_$, to indicate a constructor variable of wildcard kind. In such cases, write the second two tokens as $::\hspace{-.05in}\_$, with no intervening spaces. + For any signature item or declaration that defines some entity to be equal to $A$ with classification annotation $B$ (e.g., $\mt{val} \; x : B = A$), $B$ and the preceding colon (or similar punctuation) may be omitted, in which case it is filled in as a wildcard. A signature item or declaration $\mt{type} \; x$ or $\mt{type} \; x = \tau$ is elaborated into $\mt{con} \; x :: \mt{Type}$ or $\mt{con} \; x :: \mt{Type} = \tau$, respectively. diff --git a/src/elab.sml b/src/elab.sml index 6d405af6..dcb15502 100644 --- a/src/elab.sml +++ b/src/elab.sml @@ -39,6 +39,7 @@ datatype kind' = | KError | KUnif of ErrorMsg.span * string * kind option ref + | KTupleUnif of ErrorMsg.span * (int * kind) list * kind option ref | KRel of int | KFun of string * kind diff --git a/src/elab_print.sml b/src/elab_print.sml index 4fb7ee73..279c7231 100644 --- a/src/elab_print.sml +++ b/src/elab_print.sml @@ -56,6 +56,16 @@ fun p_kind' par env (k, _) = | KError => string "" | KUnif (_, _, ref (SOME k)) => p_kind' par env k | KUnif (_, s, _) => string ("") + | KTupleUnif (_, _, ref (SOME k)) => p_kind' par env k + | KTupleUnif (_, nks, _) => box [string "(", + p_list_sep (box [space, string "*", space]) + (fn (n, k) => box [string (Int.toString n ^ ":"), + space, + p_kind env k]) nks, + space, + string "*", + space, + string "...)"] | KRel n => ((if !debug then string (E.lookupKRel env n ^ "_" ^ Int.toString n) diff --git a/src/elab_util.sml b/src/elab_util.sml index ccfb86a3..33ed599c 100644 --- a/src/elab_util.sml +++ b/src/elab_util.sml @@ -78,6 +78,16 @@ fun mapfoldB {kind, bind} = | KUnif (_, _, ref (SOME k)) => mfk' ctx k | KUnif _ => S.return2 kAll + | KTupleUnif (_, _, ref (SOME k)) => mfk' ctx k + | KTupleUnif (loc, nks, r) => + S.map2 (ListUtil.mapfold (fn (n, k) => + S.map2 (mfk ctx k, + fn k' => + (n, k'))) nks, + fn nks' => + (KTupleUnif (loc, nks', r), loc)) + + | KRel _ => S.return2 kAll | KFun (x, k) => S.map2 (mfk (bind (ctx, x)) k, @@ -207,7 +217,7 @@ fun mapfoldB {kind = fk, con = fc, bind} = | CError => S.return2 cAll | CUnif (_, _, _, ref (SOME c)) => mfc' ctx c | CUnif _ => S.return2 cAll - + | CKAbs (x, c) => S.map2 (mfc (bind (ctx, RelK x)) c, fn c' => diff --git a/src/elaborate.sml b/src/elaborate.sml index e3f42c19..2cc01eda 100644 --- a/src/elaborate.sml +++ b/src/elaborate.sml @@ -94,6 +94,9 @@ | (L'.KUnif (_, _, ref (SOME k1All)), _) => unifyKinds' env k1All k2All | (_, L'.KUnif (_, _, ref (SOME k2All))) => unifyKinds' env k1All k2All + | (L'.KTupleUnif (_, _, ref (SOME k)), _) => unifyKinds' env k k2All + | (_, L'.KTupleUnif (_, _, ref (SOME k))) => unifyKinds' env k1All k + | (L'.KUnif (_, _, r1), L'.KUnif (_, _, r2)) => if r1 = r2 then () @@ -111,6 +114,32 @@ else r := SOME k1All + | (L'.KTupleUnif (_, nks, r), L'.KTuple ks) => + ((app (fn (n, k) => unifyKinds' env k (List.nth (ks, n-1))) nks; + r := SOME k2All) + handle Subscript => err KIncompatible) + | (L'.KTuple ks, L'.KTupleUnif (_, nks, r)) => + ((app (fn (n, k) => unifyKinds' env (List.nth (ks, n-1)) k) nks; + r := SOME k1All) + handle Subscript => err KIncompatible) + | (L'.KTupleUnif (loc, nks1, r1), L'.KTupleUnif (_, nks2, r2)) => + let + val nks = foldl (fn (p as (n, k1), nks) => + case ListUtil.search (fn (n', k2) => + if n' = n then + SOME k2 + else + NONE) nks2 of + NONE => p :: nks + | SOME k2 => (unifyKinds' env k1 k2; + nks)) nks2 nks1 + + val k = (L'.KTupleUnif (loc, nks, ref NONE), loc) + in + r1 := SOME k; + r2 := SOME k + end + | _ => err KIncompatible end @@ -441,16 +470,15 @@ | L.CProj (c, n) => let val (c', k, gs) = elabCon (env, denv) c + + val k' = kunif loc in - case hnormKind k of - (L'.KTuple ks, _) => - if n <= 0 orelse n > length ks then - (conError env (ProjBounds (c', n)); - (cerror, kerror, [])) - else - ((L'.CProj (c', n), loc), List.nth (ks, n - 1), gs) - | k => (conError env (ProjMismatch (c', k)); - (cerror, kerror, [])) + if n <= 0 then + (conError env (ProjBounds (c', n)); + (cerror, kerror, [])) + else + (checkKind env c' k (L'.KTupleUnif (loc, [(n, k')], ref NONE), loc); + ((L'.CProj (c', n), loc), k', gs)) end | L.CWild k => @@ -463,6 +491,7 @@ fun kunifsRemain k = case k of L'.KUnif (_, _, ref NONE) => true + | L'.KTupleUnif (_, _, ref NONE) => true | _ => false fun cunifsRemain c = case c of @@ -3229,6 +3258,8 @@ and wildifyStr env (str, sgn) = | L'.KError => NONE | L'.KUnif (_, _, ref (SOME k)) => decompileKind k | L'.KUnif _ => NONE + | L'.KTupleUnif (_, _, ref (SOME k)) => decompileKind k + | L'.KTupleUnif _ => NONE | L'.KRel _ => NONE | L'.KFun _ => NONE diff --git a/src/explify.sml b/src/explify.sml index 4f4f83e1..cf6c491c 100644 --- a/src/explify.sml +++ b/src/explify.sml @@ -44,6 +44,8 @@ fun explifyKind (k, loc) = | L.KError => raise Fail ("explifyKind: KError at " ^ EM.spanToString loc) | L.KUnif (_, _, ref (SOME k)) => explifyKind k | L.KUnif _ => raise Fail ("explifyKind: KUnif at " ^ EM.spanToString loc) + | L.KTupleUnif (loc, _, ref (SOME k)) => explifyKind k + | L.KTupleUnif _ => raise Fail ("explifyKind: KTupleUnif at " ^ EM.spanToString loc) | L.KRel n => (L'.KRel n, loc) | L.KFun (x, k) => (L'.KFun (x, explifyKind k), loc) diff --git a/src/urweb.grm b/src/urweb.grm index dfc22112..0c85ad7f 100644 --- a/src/urweb.grm +++ b/src/urweb.grm @@ -212,7 +212,7 @@ fun tnamesOf (e, _) = | STRING of string | INT of Int64.int | FLOAT of Real64.real | CHAR of char | SYMBOL of string | CSYMBOL of string | LPAREN | RPAREN | LBRACK | RBRACK | LBRACE | RBRACE - | EQ | COMMA | COLON | DCOLON | TCOLON | DOT | HASH | UNDER | UNDERUNDER | BAR + | EQ | COMMA | COLON | DCOLON | DCOLONWILD | TCOLON | DOT | HASH | UNDER | UNDERUNDER | BAR | PLUS | MINUS | DIVIDE | DOTDOTDOT | MOD | AT | CON | LTYPE | VAL | REC | AND | FUN | MAP | UNIT | KUNIT | CLASS | DATATYPE | OF @@ -510,6 +510,7 @@ dtypes : dtype ([dtype]) kopt : (NONE) | DCOLON kind (SOME kind) + | DCOLONWILD (SOME (KWild, s (DCOLONWILDleft, DCOLONWILDright))) dargs : ([]) | SYMBOL dargs (SYMBOL :: dargs) @@ -853,6 +854,22 @@ carg : SYMBOL DCOLON kind (fn (c, k) => ((CAbs ("_", SOME kind, c), loc), (KArrow (kind, k), loc)) end) + | SYMBOL DCOLONWILD (fn (c, k) => + let + val loc = s (SYMBOLleft, DCOLONWILDright) + val kind = (KWild, loc) + in + ((CAbs (SYMBOL, NONE, c), loc), + (KArrow (kind, k), loc)) + end) + | UNDER DCOLONWILD (fn (c, k) => + let + val loc = s (UNDERleft, DCOLONWILDright) + val kind = (KWild, loc) + in + ((CAbs ("_", NONE, c), loc), + (KArrow (kind, k), loc)) + end) | cargp (cargp) cargp : SYMBOL (fn (c, k) => @@ -1079,6 +1096,14 @@ earga : LBRACK SYMBOL RBRACK (fn (e, t) => ((ECAbs (Implicit, SYMBOL, kind, e), loc), (TCFun (Implicit, SYMBOL, kind, t), loc)) end) + | LBRACK SYMBOL DCOLONWILD RBRACK (fn (e, t) => + let + val loc = s (LBRACKleft, RBRACKright) + val kind = (KWild, loc) + in + ((ECAbs (Explicit, SYMBOL, kind, e), loc), + (TCFun (Explicit, SYMBOL, kind, t), loc)) + end) | LBRACK SYMBOL kcolon kind RBRACK(fn (e, t) => let val loc = s (LBRACKleft, RBRACKright) diff --git a/src/urweb.lex b/src/urweb.lex index 0ee09cad..a6df5f1b 100644 --- a/src/urweb.lex +++ b/src/urweb.lex @@ -372,6 +372,7 @@ xint = x[0-9a-fA-F][0-9a-fA-F]; ">=" => (Tokens.GE (pos yypos, pos yypos + size yytext)); "," => (Tokens.COMMA (pos yypos, pos yypos + size yytext)); ":::" => (Tokens.TCOLON (pos yypos, pos yypos + size yytext)); + "::_" => (Tokens.DCOLONWILD (pos yypos, pos yypos + size yytext)); "::" => (Tokens.DCOLON (pos yypos, pos yypos + size yytext)); ":" => (Tokens.COLON (pos yypos, pos yypos + size yytext)); "..." => (Tokens.DOTDOTDOT (pos yypos, pos yypos + size yytext)); diff --git a/tests/ktuple.ur b/tests/ktuple.ur new file mode 100644 index 00000000..040578e0 --- /dev/null +++ b/tests/ktuple.ur @@ -0,0 +1,2 @@ +type q = (fn p => p.1) (int, float) +type q = (fn p => p.1 * $p.3) (int, float, []) diff --git a/tests/ktuple.urp b/tests/ktuple.urp new file mode 100644 index 00000000..c466588c --- /dev/null +++ b/tests/ktuple.urp @@ -0,0 +1 @@ +ktuple -- cgit v1.2.3 From 4e608544ebe87dd991d53ded5267f14f5df93b8b Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 10 Oct 2010 20:33:10 -0400 Subject: :::_ notation; switch to TooDeep error message --- doc/manual.tex | 2 +- src/elab_err.sig | 1 + src/elab_err.sml | 2 ++ src/elaborate.sml | 4 ++-- src/urweb.grm | 12 ++++++++++-- src/urweb.lex | 1 + 6 files changed, 17 insertions(+), 5 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 9dbdb505..ebeb2d55 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -495,7 +495,7 @@ A tuple type $(\tau_1, \ldots, \tau_n)$ expands to a record type $\{1 = \tau_1, In general, several adjacent $\lambda$ forms may be combined into one, and kind and type annotations may be omitted, in which case they are implicitly included as wildcards. More formally, for constructor-level abstractions, we can define a new non-terminal $b ::= x \mid (x :: \kappa) \mid X$ and allow composite abstractions of the form $\lambda b^+ \Rightarrow c$, elaborating into the obvious sequence of one core $\lambda$ per element of $b^+$. -In some contexts, the parser isn't happy with token sequences like $x :: \_$, to indicate a constructor variable of wildcard kind. In such cases, write the second two tokens as $::\hspace{-.05in}\_$, with no intervening spaces. +In some contexts, the parser isn't happy with token sequences like $x :: \_$, to indicate a constructor variable of wildcard kind. In such cases, write the second two tokens as $::\hspace{-.05in}\_$, with no intervening spaces. Analogous syntax $:::\hspace{-.05in}\_$ is available for implicit constructor arguments. For any signature item or declaration that defines some entity to be equal to $A$ with classification annotation $B$ (e.g., $\mt{val} \; x : B = A$), $B$ and the preceding colon (or similar punctuation) may be omitted, in which case it is filled in as a wildcard. diff --git a/src/elab_err.sig b/src/elab_err.sig index fbe55a5b..3dfd5d4e 100644 --- a/src/elab_err.sig +++ b/src/elab_err.sig @@ -58,6 +58,7 @@ signature ELAB_ERR = sig | CRecordFailure of Elab.con * Elab.con * (Elab.con * Elab.con * Elab.con) option | TooLifty of ErrorMsg.span * ErrorMsg.span | TooUnify of Elab.con * Elab.con + | TooDeep val cunifyError : ElabEnv.env -> cunify_error -> unit diff --git a/src/elab_err.sml b/src/elab_err.sml index f8a16294..7d5e6be8 100644 --- a/src/elab_err.sml +++ b/src/elab_err.sml @@ -121,6 +121,7 @@ datatype cunify_error = | CRecordFailure of con * con * (con * con * con) option | TooLifty of ErrorMsg.span * ErrorMsg.span | TooUnify of con * con + | TooDeep fun cunifyError env err = case err of @@ -162,6 +163,7 @@ fun cunifyError env err = (ErrorMsg.errorAt (#2 c1) "Substitution in constructor is blocked by a too-deep unification variable"; eprefaces' [("Replacement", p_con env c1), ("Body", p_con env c2)]) + | TooDeep => ErrorMsg.error "Can't reverse-engineer unification variable lifting" datatype exp_error = UnboundExp of ErrorMsg.span * string diff --git a/src/elaborate.sml b/src/elaborate.sml index 7bf687e2..dcae4650 100644 --- a/src/elaborate.sml +++ b/src/elaborate.sml @@ -1079,13 +1079,13 @@ err COccursCheckFailed else (r := SOME (squish nl c2All) - handle CantSquish => err CIncompatible) + handle CantSquish => err (fn _ => TooDeep)) | (_, L'.CUnif (nl, _, _, _, r)) => if occursCon r c1All then err COccursCheckFailed else (r := SOME (squish nl c1All) - handle CantSquish => err CIncompatible) + handle CantSquish => err (fn _ => TooDeep)) | (L'.CUnit, L'.CUnit) => () diff --git a/src/urweb.grm b/src/urweb.grm index 0c85ad7f..21c4a50c 100644 --- a/src/urweb.grm +++ b/src/urweb.grm @@ -212,7 +212,7 @@ fun tnamesOf (e, _) = | STRING of string | INT of Int64.int | FLOAT of Real64.real | CHAR of char | SYMBOL of string | CSYMBOL of string | LPAREN | RPAREN | LBRACK | RBRACK | LBRACE | RBRACE - | EQ | COMMA | COLON | DCOLON | DCOLONWILD | TCOLON | DOT | HASH | UNDER | UNDERUNDER | BAR + | EQ | COMMA | COLON | DCOLON | DCOLONWILD | TCOLON | TCOLONWILD | DOT | HASH | UNDER | UNDERUNDER | BAR | PLUS | MINUS | DIVIDE | DOTDOTDOT | MOD | AT | CON | LTYPE | VAL | REC | AND | FUN | MAP | UNIT | KUNIT | CLASS | DATATYPE | OF @@ -394,7 +394,7 @@ fun tnamesOf (e, _) = %left ANDALSO %left ORELSE %nonassoc COLON -%nonassoc DCOLON TCOLON +%nonassoc DCOLON TCOLON DCOLONWILD TCOLONWILD %left UNION INTERSECT EXCEPT %right COMMA %right JOIN INNER CROSS OUTER LEFT RIGHT FULL @@ -1111,6 +1111,14 @@ earga : LBRACK SYMBOL RBRACK (fn (e, t) => ((ECAbs (kcolon, SYMBOL, kind, e), loc), (TCFun (kcolon, SYMBOL, kind, t), loc)) end) + | LBRACK SYMBOL TCOLONWILD RBRACK (fn (e, t) => + let + val loc = s (LBRACKleft, RBRACKright) + val kind = (KWild, loc) + in + ((ECAbs (Implicit, SYMBOL, kind, e), loc), + (TCFun (Implicit, SYMBOL, kind, t), loc)) + end) | LBRACK cexp TWIDDLE cexp RBRACK(fn (e, t) => let val loc = s (LBRACKleft, RBRACKright) diff --git a/src/urweb.lex b/src/urweb.lex index a6df5f1b..fa8c5dde 100644 --- a/src/urweb.lex +++ b/src/urweb.lex @@ -371,6 +371,7 @@ xint = x[0-9a-fA-F][0-9a-fA-F]; "<=" => (Tokens.LE (pos yypos, pos yypos + size yytext)); ">=" => (Tokens.GE (pos yypos, pos yypos + size yytext)); "," => (Tokens.COMMA (pos yypos, pos yypos + size yytext)); + ":::_" => (Tokens.TCOLONWILD (pos yypos, pos yypos + size yytext)); ":::" => (Tokens.TCOLON (pos yypos, pos yypos + size yytext)); "::_" => (Tokens.DCOLONWILD (pos yypos, pos yypos + size yytext)); "::" => (Tokens.DCOLON (pos yypos, pos yypos + size yytext)); -- cgit v1.2.3 From d15954cc4756d7ae630f36d5b72f9fbbd864b681 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 14 Oct 2010 11:54:54 -0400 Subject: Documenting limits --- CHANGELOG | 2 ++ doc/manual.tex | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) (limited to 'doc') diff --git a/CHANGELOG b/CHANGELOG index 943d7dff..eead0a5e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,8 +6,10 @@ Next - New 'onError' directive for .urp files - (* *) and comments in XML - Basis.classes, Basis.confirm, and Basis.tryDml +- New notations ::_ and :::_, for constructor parameters of unknown kind - Invocations like 'urweb foo' will compile foo.ur as a single-file project, even if no foo.urp exists +- '-limit' command-line flag and 'limit' .urp directive - Bug fixes and optimization improvements ======== diff --git a/doc/manual.tex b/doc/manual.tex index ebeb2d55..ef096356 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -146,6 +146,22 @@ Here is the complete list of directive forms. ``FFI'' stands for ``foreign func \item \texttt{include FILENAME} adds \texttt{FILENAME} to the list of files to be \texttt{\#include}d in C sources. This is most useful for interfacing with new FFI modules. \item \texttt{jsFunc Module.ident=name} gives the JavaScript name of an FFI value. \item \texttt{library FILENAME} parses \texttt{FILENAME.urp} and merges its contents with the rest of the current file's contents. If \texttt{FILENAME.urp} doesn't exist, the compiler also tries \texttt{FILENAME/lib.urp}. +\item \texttt{limit class num} sets a resource usage limit for generated applications. The limit \texttt{class} will be set to the non-negative integer \texttt{num}. The classes are: + \begin{itemize} + \item \texttt{cleanup}: maximum number of cleanup operations (e.g., entries recording the need to deallocate certain temporary objects) that may be active at once per request + \item \texttt{database}: maximum size of database files (currently only used by SQLite) + \item \texttt{deltas}: maximum number of messages sendable in a single request handler with \texttt{Basis.send} + \item \texttt{globals}: maximum number of global variables that FFI libraries may set in a single request context + \item \texttt{headers}: maximum size (in bytes) of per-request buffer used to hold HTTP headers for generated pages + \item \texttt{heap}: maximum size (in bytes) of per-request heap for dynamically-allocated data + \item \texttt{inputs}: maximum number of top-level form fields per request + \item \texttt{messages}: maximum size (in bytes) of per-request buffer used to hold a single outgoing message sent with \texttt{Basis.send} + \item \texttt{page}: maximum size (in bytes) of per-request buffer used to hold HTML content of generated pages + \item \texttt{script}: maximum size (in bytes) of per-request buffer used to hold JavaScript content of generated pages + \item \texttt{subinputs}: maximum number of form fields per request, excluding top-level fields + \item \texttt{time}: maximum running time of a single page request, in units of approximately 0.1 seconds + \item \texttt{transactionals}: maximum number of custom transactional actions (e.g., sending an e-mail) that may be run in a single page generation + \end{itemize} \item \texttt{link FILENAME} adds \texttt{FILENAME} to the list of files to be passed to the GCC linker at the end of compilation. This is most useful for importing extra libraries needed by new FFI modules. \item \texttt{onError Module.var} changes the handling of fatal application errors. Instead of displaying a default, ugly error 500 page, the error page will be generated by calling function \texttt{Module.var} on a piece of XML representing the error message. The error handler should have type $\mt{xbody} \to \mt{transaction} \; \mt{page}$. Note that the error handler \emph{cannot} be in the application's main module, since that would register it as explicitly callable via URLs. \item \texttt{path NAME=VALUE} creates a mapping from \texttt{NAME} to \texttt{VALUE}. This mapping may be used at the beginnings of filesystem paths given to various other configuration directives. A path like \texttt{\$NAME/rest} is expanded to \texttt{VALUE/rest}. There is an initial mapping from the empty name (for paths like \texttt{\$/list}) to the directory where the Ur/Web standard library is installed. If you accept the default \texttt{configure} options, this directory is \texttt{/usr/local/lib/urweb/ur}. @@ -215,6 +231,8 @@ sqlite3 path/to/database/file Date: Sat, 4 Dec 2010 11:18:19 -0500 Subject: Update manual to track uw_register_transactional() change --- doc/manual.tex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index ef096356..6b994f19 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -2139,10 +2139,11 @@ void *uw_malloc(uw_context, size_t); \item \begin{verbatim} typedef void (*uw_callback)(void *); +typedef void (*uw_callback_with_retry)(void *, int will_retry); void uw_register_transactional(uw_context, void *data, uw_callback commit, - uw_callback rollback, uw_callback free); + uw_callback rollback, uw_callback_with_retry free); \end{verbatim} - All side effects in Ur/Web programs need to be compatible with transactions, such that any set of actions can be undone at any time. Thus, you should not perform actions with non-local side effects directly; instead, register handlers to be called when the current transaction is committed or rolled back. The arguments here give an arbitary piece of data to be passed to callbacks, a function to call on commit, a function to call on rollback, and a function to call afterward in either case to clean up any allocated resources. A rollback handler may be called after the associated commit handler has already been called, if some later part of the commit process fails. + All side effects in Ur/Web programs need to be compatible with transactions, such that any set of actions can be undone at any time. Thus, you should not perform actions with non-local side effects directly; instead, register handlers to be called when the current transaction is committed or rolled back. The arguments here give an arbitary piece of data to be passed to callbacks, a function to call on commit, a function to call on rollback, and a function to call afterward in either case to clean up any allocated resources. A rollback handler may be called after the associated commit handler has already been called, if some later part of the commit process fails. A free handler is told whether the runtime system expects to retry the current page request after rollback finishes. Any of the callbacks may be \texttt{NULL}. To accommodate some stubbornly non-transactional real-world actions like sending an e-mail message, Ur/Web treats \texttt{NULL} \texttt{rollback} callbacks specially. When a transaction commits, all \texttt{commit} actions that have non-\texttt{NULL} rollback actions are tried before any \texttt{commit} actions that have \texttt{NULL} rollback actions. Thus, if a single execution uses only one non-transactional action, and if that action never fails partway through its execution while still causing an observable side effect, then Ur/Web can maintain the transactional abstraction. -- cgit v1.2.3 From 6b2fa7d163a9902a033bf2a5f86dd5fa4ef76b57 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 11 Dec 2010 11:00:05 -0500 Subject: Clarifying some C FFI details in manual --- doc/manual.tex | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 6b994f19..44fe8bdc 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -2124,18 +2124,22 @@ void uw_error(uw_context, failure_kind, const char *fmt, ...); \end{verbatim} Abort the current request processing, giving a \texttt{printf}-style format string and arguments for generating an error message. The \texttt{failure\_kind} argument can be \texttt{FATAL}, to abort the whole execution; \texttt{BOUNDED\_RETRY}, to try processing the request again from the beginning, but failing if this happens too many times; or \texttt{UNLIMITED\_RETRY}, to repeat processing, with no cap on how many times this can recur. + All pointers to the context-local heap (see description below of \texttt{uw\_malloc()}) become invalid at the start and end of any execution of a main entry point function of an application. For example, if the request handler is restarted because of a \texttt{uw\_error()} call with \texttt{BOUNDED\_RETRY} or for any other reason, it is unsafe to access any local heap pointers that may have been stashed somewhere beforehand. + \item \begin{verbatim} void uw_push_cleanup(uw_context, void (*func)(void *), void *arg); void uw_pop_cleanup(uw_context); \end{verbatim} - Manipulate a stack of actions that should be taken if any kind of error condition arises. Calling the ``pop'' function both removes an action from the stack and executes it. + Manipulate a stack of actions that should be taken if any kind of error condition arises. Calling the ``pop'' function both removes an action from the stack and executes it. It is a bug to let a page request handler finish successfully with unpopped cleanup actions. + + Pending cleanup actions aren't intended to have any complex relationship amongst themselves, so, upon request handler abort, pending actions are executed in first-in-first-out order. \item \begin{verbatim} void *uw_malloc(uw_context, size_t); \end{verbatim} - A version of \texttt{malloc()} that allocates memory inside a context's heap, which is managed with region allocation. Thus, there is no \texttt{uw\_free()}, but you need to be careful not to keep ad-hoc C pointers to this area of memory. + A version of \texttt{malloc()} that allocates memory inside a context's heap, which is managed with region allocation. Thus, there is no \texttt{uw\_free()}, but you need to be careful not to keep ad-hoc C pointers to this area of memory. In general, \texttt{uw\_malloc()}ed memory should only be used in ways compatible with the computation model of pure Ur. This means it is fine to allocate and return a value that could just as well have been built with core Ur code. In contrast, it is almost never safe to store \texttt{uw\_malloc()}ed pointers in global variables, including when the storage happens implicitly by registering a callback that would take the pointer as an argument. - For performance and correctness reasons, it is usually preferable to use \texttt{uw\_malloc()} instead of \texttt{malloc()}. The former manipulates a local heap that can be kept allocated across page requests, while the latter uses global data structures that may face contention during concurrent execution. + For performance and correctness reasons, it is usually preferable to use \texttt{uw\_malloc()} instead of \texttt{malloc()}. The former manipulates a local heap that can be kept allocated across page requests, while the latter uses global data structures that may face contention during concurrent execution. However, we emphasize again that \texttt{uw\_malloc()} should never be used to implement some logic that couldn't be implemented trivially by a constant-valued expression in Ur. \item \begin{verbatim} typedef void (*uw_callback)(void *); @@ -2147,11 +2151,15 @@ void uw_register_transactional(uw_context, void *data, uw_callback commit, Any of the callbacks may be \texttt{NULL}. To accommodate some stubbornly non-transactional real-world actions like sending an e-mail message, Ur/Web treats \texttt{NULL} \texttt{rollback} callbacks specially. When a transaction commits, all \texttt{commit} actions that have non-\texttt{NULL} rollback actions are tried before any \texttt{commit} actions that have \texttt{NULL} rollback actions. Thus, if a single execution uses only one non-transactional action, and if that action never fails partway through its execution while still causing an observable side effect, then Ur/Web can maintain the transactional abstraction. + When a request handler ends with multiple pending transactional actions, their handlers are run in a first-in-last-out stack-like order, wherever the order would otherwise be ambiguous. + + It is not safe for any of these handlers to access a context-local heap through a pointer returned previously by \texttt{uw\_malloc()}, nor should any new calls to that function be made. Think of the context-local heap as meant for use by the Ur/Web code itself, while transactional handlers execute after the Ur/Web code has finished. + \item \begin{verbatim} void *uw_get_global(uw_context, char *name); void uw_set_global(uw_context, char *name, void *data, uw_callback free); \end{verbatim} - Different FFI-based extensions may want to associate their own pieces of data with contexts. The global interface provides a way of doing that, where each extension must come up with its own unique key. The \texttt{free} argument to \texttt{uw\_set\_global()} explains how to deallocate the saved data. + Different FFI-based extensions may want to associate their own pieces of data with contexts. The global interface provides a way of doing that, where each extension must come up with its own unique key. The \texttt{free} argument to \texttt{uw\_set\_global()} explains how to deallocate the saved data. It is never safe to store \texttt{uw\_malloc()}ed pointers in global variable slots. \end{itemize} -- cgit v1.2.3 From 019d9a9c56bab7876983b78613e621d884ec20ae Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 11 Dec 2010 13:42:54 -0500 Subject: Comments in .urp files --- doc/manual.tex | 2 +- src/compiler.sml | 18 +++++++++++++----- tests/css.urp | 4 +++- 3 files changed, 17 insertions(+), 7 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 44fe8bdc..2cbfefb3 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -128,7 +128,7 @@ createdb test psql -f crud1.sql test \end{verbatim} -A blank line separates the named directives from a list of modules to include in the project. +A blank line separates the named directives from a list of modules to include in the project. Any line may contain a shell-script-style comment, where any suffix of a line starting at a hash character \texttt{\#} is ignored. For each entry \texttt{M} in the module list, the file \texttt{M.urs} is included in the project if it exists, and the file \texttt{M.ur} must exist and is always included. diff --git a/src/compiler.sml b/src/compiler.sml index c4e8977f..63db1b87 100644 --- a/src/compiler.sml +++ b/src/compiler.sml @@ -310,6 +310,14 @@ fun institutionalizeJob (job : job) = Settings.setSafeGets (#safeGets job); Settings.setOnError (#onError job)) +fun inputCommentableLine inf = + Option.map (fn s => + let + val s = #1 (Substring.splitl (fn ch => ch <> #"#") (Substring.full s)) + in + Substring.string (#1 (Substring.splitr (not o Char.isSpace) s)) + end) (TextIO.inputLine inf) + fun parseUrp' accLibs fname = if not (Posix.FileSys.access (fname ^ ".urp", []) orelse Posix.FileSys.access (fname ^ "/lib.urp", [])) andalso Posix.FileSys.access (fname ^ ".ur", []) then @@ -359,9 +367,9 @@ fun parseUrp' accLibs fname = val inf = opener () fun hasSpaceLine () = - case TextIO.inputLine inf of + case inputCommentableLine inf of NONE => false - | SOME s => s = "debug\n" orelse s = "profile\n" + | SOME s => s = "debug" orelse s = "profile" orelse CharVector.exists (fn ch => ch = #" " orelse ch = #"\t") s orelse hasSpaceLine () val hasBlankLine = hasSpaceLine () @@ -412,7 +420,7 @@ fun parseUrp' accLibs fname = OS.Path.mkAbsolute {path = pathify fname, relativeTo = absDir} fun readSources acc = - case TextIO.inputLine inf of + case inputCommentableLine inf of NONE => rev acc | SOME line => let @@ -573,9 +581,9 @@ fun parseUrp' accLibs fname = (Settings.Exact, s) fun read () = - case TextIO.inputLine inf of + case inputCommentableLine inf of NONE => finish [] - | SOME "\n" => finish (readSources []) + | SOME "" => finish (readSources []) | SOME line => let val (cmd, arg) = Substring.splitl (fn x => not (Char.isSpace x)) (Substring.full line) diff --git a/tests/css.urp b/tests/css.urp index dac138d9..08a48817 100644 --- a/tests/css.urp +++ b/tests/css.urp @@ -1 +1,3 @@ -css +# Comment here +css # Comment at end of line! +# Comments everywhere! -- cgit v1.2.3 From f327681cc734f2b478051f0174ca9d980ff2e5ae Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 11 Dec 2010 15:16:04 -0500 Subject: minHeap option in .urp files --- doc/manual.tex | 1 + include/urweb.h | 4 +--- src/c/urweb.c | 4 ++-- src/cjr_print.sml | 10 ++++++++++ src/compiler.sig | 3 ++- src/compiler.sml | 26 ++++++++++++++++++++------ src/demo.sml | 3 ++- src/settings.sig | 3 +++ src/settings.sml | 4 ++++ tests/hog.urp | 2 ++ 10 files changed, 47 insertions(+), 13 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 2cbfefb3..68e0b10c 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -163,6 +163,7 @@ Here is the complete list of directive forms. ``FFI'' stands for ``foreign func \item \texttt{transactionals}: maximum number of custom transactional actions (e.g., sending an e-mail) that may be run in a single page generation \end{itemize} \item \texttt{link FILENAME} adds \texttt{FILENAME} to the list of files to be passed to the GCC linker at the end of compilation. This is most useful for importing extra libraries needed by new FFI modules. +\item \texttt{minHeap NUMBYTES} sets the initial size for thread-local heaps used in handling requests. These heaps grow automatically as needed (up to any maximum set with \texttt{limit}), but each regrow requires restarting the request handling process. \item \texttt{onError Module.var} changes the handling of fatal application errors. Instead of displaying a default, ugly error 500 page, the error page will be generated by calling function \texttt{Module.var} on a piece of XML representing the error message. The error handler should have type $\mt{xbody} \to \mt{transaction} \; \mt{page}$. Note that the error handler \emph{cannot} be in the application's main module, since that would register it as explicitly callable via URLs. \item \texttt{path NAME=VALUE} creates a mapping from \texttt{NAME} to \texttt{VALUE}. This mapping may be used at the beginnings of filesystem paths given to various other configuration directives. A path like \texttt{\$NAME/rest} is expanded to \texttt{VALUE/rest}. There is an initial mapping from the empty name (for paths like \texttt{\$/list}) to the directory where the Ur/Web standard library is installed. If you accept the default \texttt{configure} options, this directory is \texttt{/usr/local/lib/urweb/ur}. \item \texttt{prefix PREFIX} sets the prefix included before every URI within the generated application. The default is \texttt{/}. diff --git a/include/urweb.h b/include/urweb.h index f63b3f4c..a7920851 100644 --- a/include/urweb.h +++ b/include/urweb.h @@ -284,8 +284,6 @@ void uw_set_client_data(uw_context, void *); uw_Basis_int uw_Basis_rand(uw_context); -extern int uw_time_max; - -extern int uw_supports_direct_status; +extern int uw_time_max, uw_supports_direct_status, uw_min_heap; #endif diff --git a/src/c/urweb.c b/src/c/urweb.c index 392108fe..b4a15bce 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -348,7 +348,7 @@ void uw_app_init(uw_app *app) { app->client_init(); } -int uw_time = 0, uw_time_max = 0; +int uw_time = 0, uw_time_max = 0, uw_min_heap = 0; // Single-request state @@ -461,7 +461,7 @@ uw_context uw_init() { buf_init(uw_headers_max, &ctx->outHeaders, 0); buf_init(uw_page_max, &ctx->page, 0); ctx->returning_indirectly = 0; - buf_init(uw_heap_max, &ctx->heap, 0); + buf_init(uw_heap_max, &ctx->heap, uw_min_heap); buf_init(uw_script_max, &ctx->script, 1); ctx->script.start[0] = 0; diff --git a/src/cjr_print.sml b/src/cjr_print.sml index 46de6a52..df11737e 100644 --- a/src/cjr_print.sml +++ b/src/cjr_print.sml @@ -2834,6 +2834,16 @@ fun p_file env (ds, ps) = box [string "static void uw_setup_limits() {", newline, + case Settings.getMinHeap () of + 0 => box [] + | n => box [string "uw_min_heap", + space, + string "=", + space, + string (Int.toString n), + string ";", + newline, + newline], box [p_list_sep (box []) (fn (class, num) => let val num = case class of diff --git a/src/compiler.sig b/src/compiler.sig index d0f6ac72..971ddf53 100644 --- a/src/compiler.sig +++ b/src/compiler.sig @@ -55,7 +55,8 @@ signature COMPILER = sig dbms : string option, sigFile : string option, safeGets : string list, - onError : (string * string list * string) option + onError : (string * string list * string) option, + minHeap : int } val compile : string -> bool val compiler : string -> unit diff --git a/src/compiler.sml b/src/compiler.sml index 63db1b87..655f8ced 100644 --- a/src/compiler.sml +++ b/src/compiler.sml @@ -59,7 +59,8 @@ type job = { dbms : string option, sigFile : string option, safeGets : string list, - onError : (string * string list * string) option + onError : (string * string list * string) option, + minHeap : int } type ('src, 'dst) phase = { @@ -308,14 +309,19 @@ fun institutionalizeJob (job : job) = Option.app Settings.setProtocol (#protocol job); Option.app Settings.setDbms (#dbms job); Settings.setSafeGets (#safeGets job); - Settings.setOnError (#onError job)) + Settings.setOnError (#onError job); + Settings.setMinHeap (#minHeap job)) fun inputCommentableLine inf = Option.map (fn s => let val s = #1 (Substring.splitl (fn ch => ch <> #"#") (Substring.full s)) + val s = #1 (Substring.splitr (not o Char.isSpace) s) in - Substring.string (#1 (Substring.splitr (not o Char.isSpace) s)) + Substring.string (if Substring.size s > 0 andalso Char.isSpace (Substring.sub (s, Substring.size s - 1)) then + Substring.trimr 1 s + else + s) end) (TextIO.inputLine inf) fun parseUrp' accLibs fname = @@ -349,7 +355,8 @@ fun parseUrp' accLibs fname = dbms = NONE, sigFile = NONE, safeGets = [], - onError = NONE} + onError = NONE, + minHeap = 0} in institutionalizeJob job; {Job = job, Libs = []} @@ -464,6 +471,7 @@ fun parseUrp' accLibs fname = val sigFile = ref (Settings.getSigFile ()) val safeGets = ref [] val onError = ref NONE + val minHeap = ref 0 fun finish sources = let @@ -494,7 +502,8 @@ fun parseUrp' accLibs fname = dbms = !dbms, sigFile = !sigFile, safeGets = rev (!safeGets), - onError = !onError + onError = !onError, + minHeap = !minHeap } fun mergeO f (old, new) = @@ -539,7 +548,8 @@ fun parseUrp' accLibs fname = dbms = mergeO #2 (#dbms old, #dbms new), sigFile = mergeO #2 (#sigFile old, #sigFile new), safeGets = #safeGets old @ #safeGets new, - onError = mergeO #2 (#onError old, #onError new) + onError = mergeO #2 (#onError old, #onError new), + minHeap = Int.max (#minHeap old, #minHeap new) } in if accLibs then @@ -717,6 +727,10 @@ fun parseUrp' accLibs fname = else Settings.addLimit (class, n)) | _ => ErrorMsg.error "invalid 'limit' arguments") + | "minHeap" => + (case Int.fromString arg of + NONE => ErrorMsg.error ("invalid min heap '" ^ arg ^ "'") + | SOME n => minHeap := n) | _ => ErrorMsg.error ("Unrecognized command '" ^ cmd ^ "'"); read () diff --git a/src/demo.sml b/src/demo.sml index 19632d0e..4ebdbcbc 100644 --- a/src/demo.sml +++ b/src/demo.sml @@ -118,7 +118,8 @@ fun make' {prefix, dirname, guided} = dbms = mergeWith #2 (#dbms combined, #dbms urp), sigFile = mergeWith #2 (#sigFile combined, #sigFile urp), safeGets = [], - onError = NONE + onError = NONE, + minHeap = 0 } val parse = Compiler.run (Compiler.transform Compiler.parseUrp "Demo parseUrp") diff --git a/src/settings.sig b/src/settings.sig index b72d007b..efbbdb32 100644 --- a/src/settings.sig +++ b/src/settings.sig @@ -211,4 +211,7 @@ signature SETTINGS = sig val addLimit : string * int -> unit val limits : unit -> (string * int) list + + val setMinHeap : int -> unit + val getMinHeap : unit -> int end diff --git a/src/settings.sml b/src/settings.sml index 7a943217..898b503f 100644 --- a/src/settings.sml +++ b/src/settings.sml @@ -509,4 +509,8 @@ fun addLimit (v as (name, _)) = raise Fail ("Unknown limit category '" ^ name ^ "'") fun limits () = !limitsList +val minHeap = ref 0 +fun setMinHeap n = if n >= 0 then minHeap := n else raise Fail "Trying to set negative minHeap" +fun getMinHeap () = !minHeap + end diff --git a/tests/hog.urp b/tests/hog.urp index 615fb529..edfef7f1 100644 --- a/tests/hog.urp +++ b/tests/hog.urp @@ -1 +1,3 @@ +minHeap 1000000 + hog -- cgit v1.2.3 From 6cd100d1ca2603d57ffd6a05763ca2de18554a97 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 11 Dec 2010 19:57:22 -0500 Subject: Warn about XHTML looseness --- doc/manual.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 68e0b10c..e48c2648 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1819,7 +1819,7 @@ $$\begin{array}{l} \subsection{XML} -Ur/Web's library contains an encoding of XML syntax and semantic constraints. We make no effort to follow the standards governing XML schemas. Rather, XML fragments are viewed more as values of ML datatypes, and we only track which tags are allowed inside which other tags. +Ur/Web's library contains an encoding of XML syntax and semantic constraints. We make no effort to follow the standards governing XML schemas. Rather, XML fragments are viewed more as values of ML datatypes, and we only track which tags are allowed inside which other tags. The Ur/Web standard library encodes a very loose version of XHTML, where it is very easy to produce documents which are invalid XHTML, but which still display properly in all major browsers. The main purposes of the invariants that are enforced are first, to provide some documentation about the places where it would make sense to insert XML fragments; and second, to rule out code injection attacks and other abstraction violations related to HTML syntax. The basic XML type family has arguments respectively indicating the \emph{context} of a fragment, the fields that the fragment expects to be bound on entry (and their types), and the fields that the fragment will bind (and their types). Contexts are a record-based ``poor man's subtyping'' encoding, with each possible set of valid tags corresponding to a different context record. The arguments dealing with field binding are only relevant to HTML forms. $$\begin{array}{l} -- cgit v1.2.3 From f52635b782fab64b3ef306da22ad3eb7eeeaabfe Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sun, 12 Dec 2010 10:44:19 -0500 Subject: -prefix command-line option --- doc/manual.tex | 2 ++ src/compiler.sml | 2 +- src/main.mlton.sml | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index e48c2648..2509439a 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -238,6 +238,8 @@ sqlite3 path/to/database/file NONE | s => SOME s) val database = ref (Settings.getDbstring ()) val exe = ref (Settings.getExe ()) val sql = ref (Settings.getSql ()) diff --git a/src/main.mlton.sml b/src/main.mlton.sml index a434ef12..06c04366 100644 --- a/src/main.mlton.sml +++ b/src/main.mlton.sml @@ -46,6 +46,9 @@ fun doArgs args = | "-protocol" :: name :: rest => (Settings.setProtocol name; doArgs rest) + | "-prefix" :: prefix :: rest => + (Settings.setUrlPrefix prefix; + doArgs rest) | "-db" :: s :: rest => (Settings.setDbstring (SOME s); doArgs rest) -- cgit v1.2.3 From c7996285ff4b1b05a4cecdb2c1e944f7c17b18a7 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 16 Dec 2010 10:23:37 -0500 Subject: Clarify that you aren't supposed to be able to create new XML tags --- doc/manual.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 2509439a..03219a4e 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1863,7 +1863,7 @@ $$\begin{array}{l} \hspace{.1in} \Rightarrow \mt{xml} \; \mt{ctx} \; \mt{use_1} \; \mt{bind} \to \mt{xml} \; \mt{ctx} \; (\mt{use_1} \rc \mt{use_2}) \; \mt{bind} \end{array}$$ -We will not list here the different HTML tags and related functions from the standard library. They should be easy enough to understand from the code in \texttt{basis.urs}. The set of tags in the library is not yet claimed to be complete for HTML standards. +We will not list here the different HTML tags and related functions from the standard library. They should be easy enough to understand from the code in \texttt{basis.urs}. The set of tags in the library is not yet claimed to be complete for HTML standards. Also note that there is currently no way for the programmer to add his own tags. It \emph{is} possible to add new tags directly to \texttt{basis.urs}, but this should only be done as a prelude to suggesting a patch to the main distribution. One last useful function is for aborting any page generation, returning some XML as an error message. This function takes the place of some uses of a general exception mechanism. $$\begin{array}{l} -- cgit v1.2.3 From 6aaa09dfff50fdd22aeef563de63a50926bb553f Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 16 Dec 2010 13:35:40 -0500 Subject: Fiddly tweaks --- doc/manual.tex | 2 +- lib/ur/list.ur | 13 +++++++++++++ lib/ur/list.urs | 1 + src/elab_util.sig | 23 +++++++++++++++++++++-- src/elab_util.sml | 43 +++++++++++++++++++++++++++++++++---------- src/elaborate.sml | 42 +++++++++++++++++------------------------- 6 files changed, 86 insertions(+), 38 deletions(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 03219a4e..58fbc84d 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -1823,7 +1823,7 @@ $$\begin{array}{l} Ur/Web's library contains an encoding of XML syntax and semantic constraints. We make no effort to follow the standards governing XML schemas. Rather, XML fragments are viewed more as values of ML datatypes, and we only track which tags are allowed inside which other tags. The Ur/Web standard library encodes a very loose version of XHTML, where it is very easy to produce documents which are invalid XHTML, but which still display properly in all major browsers. The main purposes of the invariants that are enforced are first, to provide some documentation about the places where it would make sense to insert XML fragments; and second, to rule out code injection attacks and other abstraction violations related to HTML syntax. -The basic XML type family has arguments respectively indicating the \emph{context} of a fragment, the fields that the fragment expects to be bound on entry (and their types), and the fields that the fragment will bind (and their types). Contexts are a record-based ``poor man's subtyping'' encoding, with each possible set of valid tags corresponding to a different context record. The arguments dealing with field binding are only relevant to HTML forms. +The basic XML type family has arguments respectively indicating the \emph{context} of a fragment, the fields that the fragment expects to be bound on entry (and their types), and the fields that the fragment will bind (and their types). Contexts are a record-based ``poor man's subtyping'' encoding, with each possible set of valid tags corresponding to a different context record. For instance, the context for the \texttt{
    }. Contexts are maintained in a somewhat ad-hoc way; the only definitive reference for their meanings is the types of the tag values in \texttt{basis.urs}. The arguments dealing with field binding are only relevant to HTML forms. $$\begin{array}{l} \mt{con} \; \mt{xml} :: \{\mt{Unit}\} \to \{\mt{Type}\} \to \{\mt{Type}\} \to \mt{Type} \end{array}$$ diff --git a/lib/ur/list.ur b/lib/ur/list.ur index 354ef132..3153cc32 100644 --- a/lib/ur/list.ur +++ b/lib/ur/list.ur @@ -322,6 +322,19 @@ val nth [a] = 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)) = diff --git a/lib/ur/list.urs b/lib/ur/list.urs index c23bf840..9ad738f1 100644 --- a/lib/ur/list.urs +++ b/lib/ur/list.urs @@ -72,6 +72,7 @@ val mapQueryPartialM : tables ::: {{Type}} -> exps ::: {Type} -> t ::: Type 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 *) diff --git a/src/elab_util.sig b/src/elab_util.sig index dc0f25e8..88bdc892 100644 --- a/src/elab_util.sig +++ b/src/elab_util.sig @@ -157,8 +157,8 @@ structure Decl : sig | NamedC of string * int * Elab.kind * Elab.con option | RelE of string * Elab.con | NamedE of string * Elab.con - | Str of string * Elab.sgn - | Sgn of string * Elab.sgn + | Str of string * int * Elab.sgn + | Sgn of string * int * Elab.sgn val mapfoldB : {kind : ('context, Elab.kind', 'state, 'abort) Search.mapfolderB, con : ('context, Elab.con', 'state, 'abort) Search.mapfolderB, @@ -203,6 +203,25 @@ structure Decl : sig decl : 'context * Elab.decl' * 'state -> Elab.decl' * 'state, bind : 'context * binder -> 'context} -> 'context -> 'state -> Elab.decl -> Elab.decl * 'state + + val map : {kind : Elab.kind' -> Elab.kind', + con : Elab.con' -> Elab.con', + exp : Elab.exp' -> Elab.exp', + sgn_item : Elab.sgn_item' -> Elab.sgn_item', + sgn : Elab.sgn' -> Elab.sgn', + str : Elab.str' -> Elab.str', + decl : Elab.decl' -> Elab.decl'} + -> Elab.decl -> Elab.decl + + val mapB : {kind : 'context -> Elab.kind' -> Elab.kind', + con : 'context -> Elab.con' -> Elab.con', + exp : 'context -> Elab.exp' -> Elab.exp', + sgn_item : 'context -> Elab.sgn_item' -> Elab.sgn_item', + sgn : 'context -> Elab.sgn' -> Elab.sgn', + str : 'context -> Elab.str' -> Elab.str', + decl : 'context -> Elab.decl' -> Elab.decl', + bind : 'context * binder -> 'context} + -> 'context -> Elab.decl -> Elab.decl end structure File : sig diff --git a/src/elab_util.sml b/src/elab_util.sml index d297ccbc..fd8163c2 100644 --- a/src/elab_util.sml +++ b/src/elab_util.sml @@ -771,8 +771,8 @@ datatype binder = | NamedC of string * int * Elab.kind * Elab.con option | RelE of string * Elab.con | NamedE of string * Elab.con - | Str of string * Elab.sgn - | Sgn of string * Elab.sgn + | Str of string * int * Elab.sgn + | Sgn of string * int * Elab.sgn fun mapfoldB {kind = fk, con = fc, exp = fe, sgn_item = fsgi, sgn = fsg, str = fst, decl = fd, bind} = let @@ -808,8 +808,8 @@ fun mapfoldB {kind = fk, con = fc, exp = fe, sgn_item = fsgi, sgn = fsg, str = f Sgn.RelK x => RelK x | Sgn.RelC x => RelC x | Sgn.NamedC x => NamedC x - | Sgn.Sgn (x, _, y) => Sgn (x, y) - | Sgn.Str (x, _, y) => Str (x, y) + | Sgn.Sgn x => Sgn x + | Sgn.Str x => Str x in bind (ctx, b') end @@ -861,12 +861,12 @@ fun mapfoldB {kind = fk, con = fc, exp = fe, sgn_item = fsgi, sgn = fsg, str = f bind (ctx, NamedE (x, c)) | DValRec vis => foldl (fn ((x, _, c, _), ctx) => bind (ctx, NamedE (x, c))) ctx vis - | DSgn (x, _, sgn) => - bind (ctx, Sgn (x, sgn)) - | DStr (x, _, sgn, _) => - bind (ctx, Str (x, sgn)) - | DFfiStr (x, _, sgn) => - bind (ctx, Str (x, sgn)) + | DSgn (x, n, sgn) => + bind (ctx, Sgn (x, n, sgn)) + | DStr (x, n, sgn, _) => + bind (ctx, Str (x, n, sgn)) + | DFfiStr (x, n, sgn) => + bind (ctx, Str (x, n, sgn)) | DConstraint _ => ctx | DExport _ => ctx | DTable (tn, x, n, c, _, pc, _, cc) => @@ -1144,6 +1144,29 @@ fun foldMapB {kind, con, exp, sgn_item, sgn, str, decl, bind} ctx st d = S.Continue x => x | S.Return _ => raise Fail "ElabUtil.Decl.foldMapB: Impossible" +fun map {kind, con, exp, sgn_item, sgn, str, decl} s = + case mapfold {kind = fn k => fn () => S.Continue (kind k, ()), + con = fn c => fn () => S.Continue (con c, ()), + exp = fn e => fn () => S.Continue (exp e, ()), + sgn_item = fn si => fn () => S.Continue (sgn_item si, ()), + sgn = fn s => fn () => S.Continue (sgn s, ()), + str = fn si => fn () => S.Continue (str si, ()), + decl = fn s => fn () => S.Continue (decl s, ())} s () of + S.Return () => raise Fail "Elab_util.Decl.map" + | S.Continue (s, ()) => s + +fun mapB {kind, con, exp, sgn_item, sgn, str, decl, bind} ctx s = + case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()), + con = fn ctx => fn c => fn () => S.Continue (con ctx c, ()), + exp = fn ctx => fn c => fn () => S.Continue (exp ctx c, ()), + sgn_item = fn ctx => fn sgi => fn () => S.Continue (sgn_item ctx sgi, ()), + sgn = fn ctx => fn s => fn () => S.Continue (sgn ctx s, ()), + str = fn ctx => fn sgi => fn () => S.Continue (str ctx sgi, ()), + decl = fn ctx => fn s => fn () => S.Continue (decl ctx s, ()), + bind = bind} ctx s () of + S.Continue (s, ()) => s + | S.Return _ => raise Fail "ElabUtil.Decl.mapB: Impossible" + end structure File = struct diff --git a/src/elaborate.sml b/src/elaborate.sml index 3134c3d4..550e3b8f 100644 --- a/src/elaborate.sml +++ b/src/elaborate.sml @@ -4262,31 +4262,17 @@ fun elabFile basis topStr topSgn env file = val (ds', env') = dopen env' {str = top_n, strs = [], sgn = topSgn} - val checks = ref ([] : (unit -> unit) list) - fun elabDecl' (d, (env, gs)) = let val () = resetKunif () val () = resetCunif () - val (ds, (env, _, gs)) = elabDecl (d, (env, D.empty, gs)) + val (ds, (env', _, gs)) = elabDecl (d, (env, D.empty, gs)) in - checks := - (fn () => - (if List.exists kunifsInDecl ds then - declError env (KunifsRemain ds) - else - (); - - case ListUtil.search cunifsInDecl ds of - NONE => () - | SOME loc => - declError env (CunifsRemain ds))) - :: !checks; - - (ds, (env, gs)) + (**) + (ds, (env', gs)) end - val (file, (_, gs)) = ListUtil.foldlMapConcat elabDecl' (env', []) file + val (file, (env'', gs)) = ListUtil.foldlMapConcat elabDecl' (env', []) file fun oneSummaryRound () = if ErrorMsg.anyErrors () then @@ -4390,10 +4376,10 @@ fun elabFile basis topStr topSgn env file = ("Hnormed 1", p_con env c1'), ("Hnormed 2", p_con env c2')]; - app (fn (loc, env, k, s1, s2) => + (*app (fn (loc, env, k, s1, s2) => eprefaces' [("s1", p_summary env (normalizeRecordSummary env s1)), ("s2", p_summary env (normalizeRecordSummary env s2))]) - (!delayedUnifs); + (!delayedUnifs);*) if (isUnif c1' andalso maybeAttr c2') orelse (isUnif c2' andalso maybeAttr c1') then @@ -4422,10 +4408,16 @@ fun elabFile basis topStr topSgn env file = (!delayedUnifs); delayedUnifs := []); - if ErrorMsg.anyErrors () then - () - else - app (fn f => f ()) (!checks); + ignore (List.exists (fn d => if kunifsInDecl d then + (declError env'' (KunifsRemain [d]); + true) + else + false) file); + + ignore (List.exists (fn d => case cunifsInDecl d of + NONE => false + | SOME _ => (declError env'' (CunifsRemain [d]); + true)) file); if ErrorMsg.anyErrors () then () @@ -4437,7 +4429,7 @@ fun elabFile basis topStr topSgn env file = (!delayedExhaustives); (*preface ("file", p_file env' file);*) - + (L'.DFfiStr ("Basis", basis_n, sgn), ErrorMsg.dummySpan) :: ds @ (L'.DStr ("Top", top_n, topSgn, topStr), ErrorMsg.dummySpan) -- cgit v1.2.3 From c71de1db0cf31466bfc5fe7e96021e5d3cba6979 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 18 Dec 2010 10:56:31 -0500 Subject: postBody type --- doc/manual.tex | 4 +- include/types.h | 4 ++ include/urweb.h | 7 ++++ lib/ur/basis.urs | 4 ++ src/c/request.c | 5 ++- src/c/urweb.c | 34 ++++++++++++++++ src/cjr_print.sml | 107 ++++++++++++++++++++++++++------------------------- src/corify.sml | 10 ++++- src/effectize.sml | 9 +++++ src/elaborate.sml | 8 +++- src/export.sig | 1 + src/export.sml | 2 + src/marshalcheck.sml | 7 +++- tests/post.ur | 5 +++ tests/post.urp | 1 + tests/post.urs | 1 + 16 files changed, 150 insertions(+), 59 deletions(-) create mode 100644 tests/post.ur create mode 100644 tests/post.urp create mode 100644 tests/post.urs (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 58fbc84d..a8d799d8 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -2077,6 +2077,8 @@ $$\begin{array}{rrcll} A web application is built from a series of modules, with one module, the last one appearing in the \texttt{.urp} file, designated as the main module. The signature of the main module determines the URL entry points to the application. Such an entry point should have type $\mt{t1} \to \ldots \to \mt{tn} \to \mt{transaction} \; \mt{page}$, for any integer $n \geq 0$, where $\mt{page}$ is a type synonym for top-level HTML pages, defined in $\mt{Basis}$. If such a function is at the top level of main module $M$, with $n = 0$, it will be accessible at URI \texttt{/M/f}, and so on for more deeply-nested functions, as described in Section \ref{tag} below. Arguments to an entry-point function are deserialized from the part of the URI following \texttt{f}. +Normal links are accessible via HTTP \texttt{GET}, which the relevant standard says should never cause side effects. To export a page which may cause side effects, accessible only via HTTP \texttt{POST}, include one argument of the page handler of type $\mt{Basis.postBody}$. When the handler is called, this argument will receive a value that can be deconstructed into a MIME type (with $\mt{Basis.postType}$) and payload (with $\mt{Basis.postData}$). This kind of handler will only work with \texttt{POST} payloads of MIME types besides those associated with HTML forms; for these, use Ur/Web's built-in support, as described below. + When the standalone web server receives a request for a known page, it calls the function for that page, ``running'' the resulting transaction to produce the page to return to the client. Pages link to other pages with the \texttt{link} attribute of the \texttt{a} HTML tag. A link has type $\mt{transaction} \; \mt{page}$, and the semantics of a link are that this transaction should be run to compute the result page, when the link is followed. Link targets are assigned URL names in the same way as top-level entry points. HTML forms are handled in a similar way. The $\mt{action}$ attribute of a $\mt{submit}$ form tag takes a value of type $\$\mt{use} \to \mt{transaction} \; \mt{page}$, where $\mt{use}$ is a kind-$\{\mt{Type}\}$ record of the form fields used by this action handler. Action handlers are assigned URL patterns in the same way as above. @@ -2087,7 +2089,7 @@ Ur/Web programs generally mix server- and client-side code in a fairly transpare \medskip -The HTTP standard suggests that GET requests only be used in ways that generate no side effects. Side effecting operations should use POST requests instead. The Ur/Web compiler enforces this rule strictly, via a simple conservative program analysis. Any page that may have a side effect must be accessed through a form, all of which use POST requests. A page is judged to have a side effect if its code depends syntactically on any of the side-effecting, server-side FFI functions. Links, forms, and most client-side event handlers are not followed during this syntactic traversal, but \texttt{} handlers \emph{are} examined, since they run right away and could just as well be considered parts of main page handlers. +The HTTP standard suggests that GET requests only be used in ways that generate no side effects. Side effecting operations should use POST requests instead. The Ur/Web compiler enforces this rule strictly, via a simple conservative program analysis. Any page that may have a side effect must be accessed through a form, all of which use POST requests, or via a direct call to a page handler with some argument of type $\mt{Basis.postBody}$. A page is judged to have a side effect if its code depends syntactically on any of the side-effecting, server-side FFI functions. Links, forms, and most client-side event handlers are not followed during this syntactic traversal, but \texttt{} handlers \emph{are} examined, since they run right away and could just as well be considered parts of main page handlers. Ur/Web includes a kind of automatic protection against cross site request forgery attacks. Whenever any page execution can have side effects and can also read at least one cookie value, all cookie values must be signed cryptographically, to ensure that the user has come to the current page by submitting a form on a real page generated by the proper server. Signing and signature checking are inserted automatically by the compiler. This prevents attacks like phishing schemes where users are directed to counterfeit pages with forms that submit to your application, where a user's cookies might be submitted without his knowledge, causing some undesired side effect. diff --git a/include/types.h b/include/types.h index e5edab96..2adda753 100644 --- a/include/types.h +++ b/include/types.h @@ -40,6 +40,10 @@ typedef struct uw_Basis_file { uw_Basis_blob data; } uw_Basis_file; +typedef struct uw_Basis_postBody { + uw_Basis_string type, data; +} uw_Basis_postBody; + typedef enum { SUCCESS, FATAL, BOUNDED_RETRY, UNLIMITED_RETRY, RETURN_INDIRECTLY } failure_kind; typedef enum { SERVED, KEEP_OPEN, FAILED } request_result; diff --git a/include/urweb.h b/include/urweb.h index 9527fac1..a0decd11 100644 --- a/include/urweb.h +++ b/include/urweb.h @@ -221,6 +221,13 @@ uw_Basis_blob uw_Basis_fileData(uw_context, uw_Basis_file); uw_Basis_int uw_Basis_blobSize(uw_context, uw_Basis_blob); uw_Basis_blob uw_Basis_textBlob(uw_context, uw_Basis_string); +uw_Basis_string uw_Basis_postType(uw_context, uw_Basis_postBody); +uw_Basis_string uw_Basis_postData(uw_context, uw_Basis_postBody); +void uw_noPostBody(uw_context); +void uw_postBody(uw_context, uw_Basis_postBody); +int uw_hasPostBody(uw_context); +uw_Basis_postBody uw_getPostBody(uw_context); + __attribute__((noreturn)) void uw_return_blob(uw_context, uw_Basis_blob, uw_Basis_string mimeType); __attribute__((noreturn)) void uw_redirect(uw_context, uw_Basis_string url); diff --git a/lib/ur/basis.urs b/lib/ur/basis.urs index 6cd9915e..6fa92a7c 100644 --- a/lib/ur/basis.urs +++ b/lib/ur/basis.urs @@ -737,6 +737,10 @@ 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 + con radio = [Body, Radio] val radio : formTag string radio [Id = string] val radioOption : unit -> tag ([Value = string, Checked = bool] ++ boxAttrs) radio [] [] [] diff --git a/src/c/request.c b/src/c/request.c index bcfec5e9..e51f95ae 100644 --- a/src/c/request.c +++ b/src/c/request.c @@ -192,6 +192,9 @@ request_result uw_request(uw_request_context rc, uw_context ctx, boundary[0] = '-'; boundary[1] = '-'; boundary_len = strlen(boundary); + } else if (clen_s && strcasecmp(clen_s, "application/x-www-form-urlencoded")) { + uw_Basis_postBody pb = {clen_s, body}; + uw_postBody(ctx, pb); } } else if (strcmp(method, "GET")) { log_error(logger_data, "Not ready for non-GET/POST command: %s\n", method); @@ -325,7 +328,7 @@ request_result uw_request(uw_request_context rc, uw_context ctx, } } } - else { + else if (!uw_hasPostBody(ctx)) { inputs = is_post ? body : query_string; if (inputs) { diff --git a/src/c/urweb.c b/src/c/urweb.c index 47c6dadf..2b54e87c 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -445,6 +445,9 @@ struct uw_context { void *logger_data; uw_logger log_debug; + int hasPostBody; + uw_Basis_postBody postBody; + char error_message[ERROR_BUF_LEN]; }; @@ -507,6 +510,8 @@ uw_context uw_init(void *logger_data, uw_logger log_debug) { ctx->logger_data = logger_data; ctx->log_debug = log_debug; + ctx->hasPostBody = 0; + return ctx; } @@ -583,6 +588,7 @@ void uw_reset_keep_error_message(uw_context ctx) { ctx->cur_container = NULL; ctx->used_transactionals = 0; ctx->script_header = ""; + ctx->hasPostBody = 0; } void uw_reset_keep_request(uw_context ctx) { @@ -3200,6 +3206,14 @@ uw_Basis_blob uw_Basis_fileData(uw_context ctx, uw_Basis_file f) { return f.data; } +uw_Basis_string uw_Basis_postType(uw_context ctx, uw_Basis_postBody pb) { + return pb.type; +} + +uw_Basis_string uw_Basis_postData(uw_context ctx, uw_Basis_postBody pb) { + return pb.data; +} + __attribute__((noreturn)) void uw_return_blob(uw_context ctx, uw_Basis_blob b, uw_Basis_string mimeType) { cleanup *cl; int len; @@ -3458,3 +3472,23 @@ uw_Basis_int uw_Basis_rand(uw_context ctx) { uw_Basis_int n = abs(rand()); return n; } + +void uw_noPostBody(uw_context ctx) { + ctx->hasPostBody = 0; +} + +void uw_postBody(uw_context ctx, uw_Basis_postBody pb) { + ctx->hasPostBody = 1; + ctx->postBody = pb; +} + +int uw_hasPostBody(uw_context ctx) { + return ctx->hasPostBody; +} + +uw_Basis_postBody uw_getPostBody(uw_context ctx) { + if (ctx->hasPostBody) + return ctx->postBody; + else + uw_error(ctx, FATAL, "Asked for POST body when none exists"); +} diff --git a/src/cjr_print.sml b/src/cjr_print.sml index df11737e..fbbbc548 100644 --- a/src/cjr_print.sml +++ b/src/cjr_print.sml @@ -2246,22 +2246,21 @@ fun p_file env (ds, ps) = val fields = foldl (fn ((ek, _, _, ts, _, _, _), fields) => case ek of - Link => fields - | Rpc _ => fields - | Action eff => - case List.nth (ts, length ts - 2) of - (TRecord i, loc) => - let - val xts = E.lookupStruct env i - val extra = case eff of - ReadCookieWrite => [sigName xts] - | _ => [] - in - case flatFields extra (TRecord i, loc) of - NONE => raise Fail "CjrPrint: flatFields impossible" - | SOME fields' => List.revAppend (fields', fields) - end - | _ => raise Fail "CjrPrint: Last argument of action isn't record") + Action eff => + (case List.nth (ts, length ts - 2) of + (TRecord i, loc) => + let + val xts = E.lookupStruct env i + val extra = case eff of + ReadCookieWrite => [sigName xts] + | _ => [] + in + case flatFields extra (TRecord i, loc) of + NONE => raise Fail "CjrPrint: flatFields impossible" + | SOME fields' => List.revAppend (fields', fields) + end + | _ => raise Fail "CjrPrint: Last argument of action isn't record") + | _ => fields) [] ps val fields = foldl (fn (xts, fields) => @@ -2544,49 +2543,49 @@ fun p_file env (ds, ps) = let val (ts, defInputs, inputsVar, fields) = case ek of - Core.Link => (List.take (ts, length ts - 1), string "", string "", NONE) - | Core.Rpc _ => (List.take (ts, length ts - 1), string "", string "", NONE) - | Core.Action _ => - case List.nth (ts, length ts - 2) of - (TRecord i, _) => - let - val xts = E.lookupStruct env i - in - (List.take (ts, length ts - 2), - box [box (map (fn (x, t) => box [p_typ env t, - space, - string "uw_input_", - p_ident x, - string ";", - newline]) xts), - newline, - box (map getInput xts), - string "struct __uws_", - string (Int.toString i), - space, - string "uw_inputs", - space, - string "= {", - newline, - box (map (fn (x, _) => box [string "uw_input_", - p_ident x, - string ",", - newline]) xts), - string "};", - newline], - box [string ",", - space, - string "uw_inputs"], - SOME xts) - end + Core.Action _ => + (case List.nth (ts, length ts - 2) of + (TRecord i, _) => + let + val xts = E.lookupStruct env i + in + (List.take (ts, length ts - 2), + box [box (map (fn (x, t) => box [p_typ env t, + space, + string "uw_input_", + p_ident x, + string ";", + newline]) xts), + newline, + box (map getInput xts), + string "struct __uws_", + string (Int.toString i), + space, + string "uw_inputs", + space, + string "= {", + newline, + box (map (fn (x, _) => box [string "uw_input_", + p_ident x, + string ",", + newline]) xts), + string "};", + newline], + box [string ",", + space, + string "uw_inputs"], + SOME xts) + end - | _ => raise Fail "CjrPrint: Last argument to an action isn't a record" + | _ => raise Fail "CjrPrint: Last argument to an action isn't a record") + | _ => (List.take (ts, length ts - 1), string "", string "", NONE) fun couldWrite ek = case ek of Link => false | Action ef => ef = ReadCookieWrite | Rpc ef => ef = ReadCookieWrite + | Extern ef => ef = ReadCookieWrite val s = case Settings.getUrlPrefix () of @@ -2693,7 +2692,9 @@ fun p_file env (ds, ps) = space, string "=", space, - unurlify false env t, + case #1 t of + TFfi ("Basis", "postBody") => string "uw_getPostBody(ctx)" + | _ => unurlify false env t, string ";", newline]) ts), defInputs, diff --git a/src/corify.sml b/src/corify.sml index c3a53094..075047a2 100644 --- a/src/corify.sml +++ b/src/corify.sml @@ -1011,11 +1011,19 @@ fun corifyDecl mods (all as (d, loc : EM.span), st) = t, tf, e), loc), (L.TFun (t, tf), loc))) ((L.EApp (ef, ea), loc), ranT) args + + val expKind = if List.exists (fn t => + case corifyCon st t of + (L'.CFfi ("Basis", "postBody"), _) => true + | _ => false) args then + L'.Extern L'.ReadCookieWrite + else + L'.Link in ((L.DVal ("wrap_" ^ s, 0, tf, e), loc) :: wds, (fn st => case #1 (corifyExp st (L.EModProj (en, [], "wrap_" ^ s), loc)) of - L'.ENamed n => (L'.DExport (L'.Link, n, false), loc) + L'.ENamed n => (L'.DExport (expKind, n, false), loc) | _ => raise Fail "Corify: Value to export didn't corify properly") :: eds) end diff --git a/src/effectize.sml b/src/effectize.sml index 7f148476..4601f301 100644 --- a/src/effectize.sml +++ b/src/effectize.sml @@ -168,6 +168,15 @@ fun effectize file = else ReadOnly), n, IM.inDomain (pushers, n)), #2 d), evs) + | DExport (Extern _, n, _) => + ((DExport (Extern (if IM.inDomain (writers, n) then + if IM.inDomain (readers, n) then + ReadCookieWrite + else + ReadWrite + else + ReadOnly), n, IM.inDomain (pushers, n)), #2 d), + evs) | _ => (d, evs) val (file, _) = ListUtil.foldlMap doDecl (IM.empty, IM.empty, IM.empty) file diff --git a/src/elaborate.sml b/src/elaborate.sml index 008529ea..b1515b6e 100644 --- a/src/elaborate.sml +++ b/src/elaborate.sml @@ -3834,8 +3834,14 @@ and elabDecl (dAll as (d, loc), (env, denv, gs)) = (L'.CModProj (basis, [], "transaction"), loc), t), loc) + + fun normArgs t = + case hnormCon env t of + (L'.TFun (dom, ran), loc) => + (L'.TFun (hnormCon env dom, normArgs ran), loc) + | t' => t' in - (L'.SgiVal (x, n, makeRes t), loc) + (L'.SgiVal (x, n, normArgs (makeRes t)), loc) end | _ => all) | _ => all) diff --git a/src/export.sig b/src/export.sig index 0bbdd1ac..9bcfa0d4 100644 --- a/src/export.sig +++ b/src/export.sig @@ -36,6 +36,7 @@ datatype export_kind = Link | Action of effect | Rpc of effect + | Extern of effect val p_effect : effect Print.printer val p_export_kind : export_kind Print.printer diff --git a/src/export.sml b/src/export.sml index ad604e16..5d200894 100644 --- a/src/export.sml +++ b/src/export.sml @@ -39,6 +39,7 @@ datatype export_kind = Link | Action of effect | Rpc of effect + | Extern of effect fun p_effect ef = case ef of @@ -51,5 +52,6 @@ fun p_export_kind ck = Link => string "link" | Action ef => box [string "action(", p_effect ef, string ")"] | Rpc ef => box [string "rpc(", p_effect ef, string ")"] + | Extern ef => box [string "extern(", p_effect ef, string ")"] end diff --git a/src/marshalcheck.sml b/src/marshalcheck.sml index de6879ae..c7a274de 100644 --- a/src/marshalcheck.sml +++ b/src/marshalcheck.sml @@ -1,4 +1,4 @@ -(* Copyright (c) 2009, Adam Chlipala +(* Copyright (c) 2009-2010, Adam Chlipala * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -96,7 +96,10 @@ fun check file = let fun makeS (t, _) = case t of - TFun (dom, ran) => PS.union (sins cmap dom, makeS ran) + TFun (dom, ran) => + (case #1 dom of + CFfi ("Basis", "postBody") => makeS ran + | _ => PS.union (sins cmap dom, makeS ran)) | _ => PS.empty val s = makeS t in diff --git a/tests/post.ur b/tests/post.ur new file mode 100644 index 00000000..4cee7a45 --- /dev/null +++ b/tests/post.ur @@ -0,0 +1,5 @@ +fun callMe n s pb = return + n = {[n]}
    + s = {[s]}
    + pb : {[postType pb]} = {[postData pb]} +
    diff --git a/tests/post.urp b/tests/post.urp new file mode 100644 index 00000000..8b1e502f --- /dev/null +++ b/tests/post.urp @@ -0,0 +1 @@ +post diff --git a/tests/post.urs b/tests/post.urs new file mode 100644 index 00000000..5d6e6460 --- /dev/null +++ b/tests/post.urs @@ -0,0 +1 @@ +val callMe : int -> string -> postBody -> transaction page -- cgit v1.2.3 From 4d07c227812b49e71de49b3e64ec6da1fbc30aed Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 18 Dec 2010 14:17:45 -0500 Subject: Change tasks to support parametric code; add clientLeaves --- doc/manual.tex | 22 ++++++++++++++-- lib/ur/basis.urs | 5 ++-- src/cjr.sml | 4 +-- src/cjr_print.sml | 72 ++++++++++++++++++++++++++++++++++++---------------- src/cjrize.sml | 7 ++--- src/elaborate.sml | 5 ++++ src/prepare.sml | 4 +-- tests/init.ur | 2 +- tests/initSimple.ur | 3 +++ tests/initSimple.urp | 1 + tests/initSimple.urs | 1 + tests/roundTrip.ur | 12 +++++++-- 12 files changed, 102 insertions(+), 36 deletions(-) create mode 100644 tests/initSimple.ur create mode 100644 tests/initSimple.urp create mode 100644 tests/initSimple.urs (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index a8d799d8..5a7110cb 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -962,8 +962,8 @@ $$\infer{\Gamma \vdash \mt{cookie} \; x : \tau \leadsto \Gamma, x : \mt{Basis}.\ \quad \infer{\Gamma \vdash \mt{style} \; x \leadsto \Gamma, x : \mt{Basis}.\mt{css\_class}}{}$$ $$\infer{\Gamma \vdash \mt{task} \; e_1 = e_2 \leadsto \Gamma}{ - \Gamma \vdash e_1 :: \mt{Basis}.\mt{task\_kind} - & \Gamma \vdash e_2 :: \mt{Basis}.\mt{transaction} \; \{\} + \Gamma \vdash e_1 :: \mt{Basis}.\mt{task\_kind} \; \tau + & \Gamma \vdash e_2 :: \tau \to \mt{Basis}.\mt{transaction} \; \{\} }$$ $$\infer{\Gamma \vdash \mt{class} \; x :: \kappa = c \leadsto \Gamma, x :: \kappa = c}{ @@ -2093,6 +2093,24 @@ The HTTP standard suggests that GET requests only be used in ways that generate Ur/Web includes a kind of automatic protection against cross site request forgery attacks. Whenever any page execution can have side effects and can also read at least one cookie value, all cookie values must be signed cryptographically, to ensure that the user has come to the current page by submitting a form on a real page generated by the proper server. Signing and signature checking are inserted automatically by the compiler. This prevents attacks like phishing schemes where users are directed to counterfeit pages with forms that submit to your application, where a user's cookies might be submitted without his knowledge, causing some undesired side effect. +\subsection{Tasks} + +In many web applications, it's useful to run code at points other than requests from browsers. Ur/Web's \emph{task} mechanism facilitates this. A type family of \emph{task kinds} is in the standard library: + +$$\begin{array}{l} +\mt{con} \; \mt{task\_kind} :: \mt{Type} \to \mt{Type} \\ +\mt{val} \; \mt{initialize} : \mt{task\_kind} \; \mt{unit} \\ +\mt{val} \; \mt{clientLeaves} : \mt{task\_kind} \; \mt{client} +\end{array}$$ + +A task kind names a particular extension point of generated applications, where the type parameter of a task kind describes which extra input data is available at that extension point. Add task code with the special declaration form $\mt{task} \; e_1 = e_2$, where $e_1$ is a task kind with data $\tau$, and $e_2$ is a function from $\tau$ to $\mt{transaction} \; \mt{unit}$. + +The currently supported task kinds are: +\begin{itemize} +\item $\mt{initialize}$: Code that is run in each freshly-allocated request context. +\item $\mt{clientLeaves}$: Code that is run for each client that the runtime system decides has surfed away. When a request that generates a new client handle is aborted, that handle will still eventually be passed to $\mt{clientLeaves}$ task code, even though the corresponding browser was never informed of the client handle's existence. In other words, in general, $\mt{clientLeaves}$ handlers will be called more times than there are actual clients. +\end{itemize} + \section{The Foreign Function Interface} diff --git a/lib/ur/basis.urs b/lib/ur/basis.urs index 6fa92a7c..2a61b701 100644 --- a/lib/ur/basis.urs +++ b/lib/ur/basis.urs @@ -810,8 +810,9 @@ val show_xml : ctx ::: {Unit} -> use ::: {Type} -> bind ::: {Type} -> show (xml (** Tasks *) -type task_kind -val initialize : task_kind +con task_kind :: Type -> Type +val initialize : task_kind unit +val clientLeaves : task_kind client (** Information flow security *) diff --git a/src/cjr.sml b/src/cjr.sml index 5013033f..c57128cf 100644 --- a/src/cjr.sml +++ b/src/cjr.sml @@ -103,7 +103,7 @@ datatype exp' = withtype exp = exp' located -datatype task = Initialize +datatype task = Initialize | ClientLeaves datatype decl' = DStruct of int * (string * typ) list @@ -123,7 +123,7 @@ datatype decl' = | DCookie of string | DStyle of string - | DTask of task * exp + | DTask of task * string (* first arg name *) * string * exp | DOnError of int withtype decl = decl' located diff --git a/src/cjr_print.sml b/src/cjr_print.sml index fbbbc548..2bb5775e 100644 --- a/src/cjr_print.sml +++ b/src/cjr_print.sml @@ -2794,7 +2794,8 @@ fun p_file env (ds, ps) = string "}", newline] - val initializers = List.mapPartial (fn (DTask (Initialize, e), _) => SOME e | _ => NONE) ds + val initializers = List.mapPartial (fn (DTask (Initialize, x1, x2, e), _) => SOME (x1, x2, e) | _ => NONE) ds + val expungers = List.mapPartial (fn (DTask (ClientLeaves, x1, x2, e), _) => SOME (x1, x2, e) | _ => NONE) ds val onError = ListUtil.search (fn (DOnError n, _) => SOME n | _ => NONE) ds @@ -2968,31 +2969,58 @@ fun p_file env (ds, ps) = newline, newline, - if hasDb then - box [string "static void uw_expunger(uw_context ctx, uw_Basis_client cli) {", - newline, + box [string "static void uw_expunger(uw_context ctx, uw_Basis_client cli) {", + newline, + + p_list_sep (box []) (fn (x1, x2, e) => box [string "({", + newline, + string "uw_Basis_client __uwr_", + string x1, + string "_0 = cli;", + newline, + string "uw_unit __uwr_", + string x2, + string "_1 = uw_unit_v;", + newline, + p_exp (E.pushERel (E.pushERel env x1 (TFfi ("Basis", "client"), ErrorMsg.dummySpan)) + x2 dummyt) e, + string ";", + newline, + string "});", + newline]) expungers, + + if hasDb then box [p_enamed env (!expunge), string "(ctx, cli);", - newline], - string "}", - newline, - newline, + newline] + else + box [], + string "}"], - string "static void uw_initializer(uw_context ctx) {", - newline, - box [p_list_sep (box []) (fn e => box [p_exp env e, - string ";", - newline]) initializers, - p_enamed env (!initialize), + newline, + string "static void uw_initializer(uw_context ctx) {", + newline, + box [p_list_sep (box []) (fn (x1, x2, e) => box [string "({", + newline, + string "uw_unit __uwr_", + string x1, + string "_0 = uw_unit_v, __uwr_", + string x2, + string "_1 = uw_unit_v;", + newline, + p_exp (E.pushERel (E.pushERel env x1 dummyt) x2 dummyt) e, + string ";", + newline, + string "});", + newline]) initializers, + if hasDb then + box [p_enamed env (!initialize), string "(ctx, uw_unit_v);", - newline], - string "}", - newline] - else - box [string "static void uw_expunger(uw_context ctx, uw_Basis_client cli) { };", - newline, - string "static void uw_initializer(uw_context ctx) { };", - newline], + newline] + else + box []], + string "}", + newline, case onError of NONE => box [] diff --git a/src/cjrize.sml b/src/cjrize.sml index 2915b0ca..0505af62 100644 --- a/src/cjrize.sml +++ b/src/cjrize.sml @@ -1,4 +1,4 @@ -(* Copyright (c) 2008, Adam Chlipala +(* Copyright (c) 2008-2010, Adam Chlipala * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -662,15 +662,16 @@ fun cifyDecl ((d, loc), sm) = | L.DStyle args => (SOME (L'.DStyle args, loc), NONE, sm) | L.DTask (e1, e2) => (case #1 e2 of - L.EAbs (_, _, _, e) => + L.EAbs (x1, _, _, (L.EAbs (x2, _, _, e), _)) => let val tk = case #1 e1 of L.EFfi ("Basis", "initialize") => L'.Initialize + | L.EFfi ("Basis", "clientLeaves") => L'.ClientLeaves | _ => (ErrorMsg.errorAt loc "Task kind not fully determined"; L'.Initialize) val (e, sm) = cifyExp (e, sm) in - (SOME (L'.DTask (tk, e), loc), NONE, sm) + (SOME (L'.DTask (tk, x1, x2, e), loc), NONE, sm) end | _ => (ErrorMsg.errorAt loc "Initializer has not been fully determined"; (NONE, NONE, sm))) diff --git a/src/elaborate.sml b/src/elaborate.sml index b1515b6e..76e22139 100644 --- a/src/elaborate.sml +++ b/src/elaborate.sml @@ -3962,9 +3962,14 @@ and elabDecl (dAll as (d, loc), (env, denv, gs)) = val (e1', t1, gs1) = elabExp (env, denv) e1 val (e2', t2, gs2) = elabExp (env, denv) e2 + val targ = cunif (loc, (L'.KType, loc)) + val t1' = (L'.CModProj (!basis_r, [], "task_kind"), loc) + val t1' = (L'.CApp (t1', targ), loc) + val t2' = (L'.CApp ((L'.CModProj (!basis_r, [], "transaction"), loc), (L'.TRecord (L'.CRecord ((L'.KType, loc), []), loc), loc)), loc) + val t2' = (L'.TFun (targ, t2'), loc) in checkCon env e1' t1 t1'; checkCon env e2' t2 t2'; diff --git a/src/prepare.sml b/src/prepare.sml index 4d81940f..1b7454dc 100644 --- a/src/prepare.sml +++ b/src/prepare.sml @@ -325,11 +325,11 @@ fun prepDecl (d as (_, loc), st) = | DJavaScript _ => (d, st) | DCookie _ => (d, st) | DStyle _ => (d, st) - | DTask (tk, e) => + | DTask (tk, x1, x2, e) => let val (e, st) = prepExp (e, st) in - ((DTask (tk, e), loc), st) + ((DTask (tk, x1, x2, e), loc), st) end | DOnError _ => (d, st) diff --git a/tests/init.ur b/tests/init.ur index aafbb55f..8040612d 100644 --- a/tests/init.ur +++ b/tests/init.ur @@ -1,6 +1,6 @@ sequence seq table fred : {A : int, B : int} -task initialize = +task initialize = fn () => setval seq 1; dml (INSERT INTO fred (A, B) VALUES (0, 1)) diff --git a/tests/initSimple.ur b/tests/initSimple.ur new file mode 100644 index 00000000..e1c94280 --- /dev/null +++ b/tests/initSimple.ur @@ -0,0 +1,3 @@ +task initialize = fn () => debug "I ran!" + +fun main () = return Hi! diff --git a/tests/initSimple.urp b/tests/initSimple.urp new file mode 100644 index 00000000..c2374400 --- /dev/null +++ b/tests/initSimple.urp @@ -0,0 +1 @@ +initSimple diff --git a/tests/initSimple.urs b/tests/initSimple.urs new file mode 100644 index 00000000..901d6bf2 --- /dev/null +++ b/tests/initSimple.urs @@ -0,0 +1 @@ +val main : {} -> transaction page diff --git a/tests/roundTrip.ur b/tests/roundTrip.ur index d22b2d41..0ee3f8f7 100644 --- a/tests/roundTrip.ur +++ b/tests/roundTrip.ur @@ -1,12 +1,18 @@ table channels : { Client : client, Channel : channel (string * int * float) } PRIMARY KEY Client +table dearlyDeparted : { Client : option client, When : time } + +task clientLeaves = fn cli : client => + dml (INSERT INTO dearlyDeparted (Client, When) VALUES ({[Some cli]}, CURRENT_TIMESTAMP)); + debug "Our favorite client has LEFT!" + fun writeBack v = me <- self; r <- oneRow (SELECT channels.Channel FROM channels WHERE channels.Client = {[me]}); send r.Channels.Channel v -fun main () = +fun main' () = me <- self; ch <- channel; dml (INSERT INTO channels (Client, Channel) VALUES ({[me]}, {[ch]})); @@ -27,7 +33,7 @@ fun main () = fun sender s n f = sleep 2000; - writeBack (s, n, f); + rpc (writeBack (s, n, f)); sender (s ^ "!") (n + 1) (f + 1.23) in return end + +fun main () = return -- cgit v1.2.3 From 3c8e408d34b54df57a700813636dd78ddc26c45b Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Sat, 18 Dec 2010 15:17:09 -0500 Subject: Periodic tasks --- doc/manual.tex | 6 ++++-- include/types.h | 7 +++++++ include/urweb.h | 2 ++ lib/ur/basis.urs | 1 + src/c/request.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++------ src/c/urweb.c | 16 ++++++++++++++++ src/cjr.sml | 2 +- src/cjr_print.sml | 33 ++++++++++++++++++++++++++++++++- src/cjrize.sml | 1 + tests/periodic.ur | 4 ++++ 10 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 tests/periodic.ur (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index 5a7110cb..f86ea97e 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -2100,15 +2100,17 @@ In many web applications, it's useful to run code at points other than requests $$\begin{array}{l} \mt{con} \; \mt{task\_kind} :: \mt{Type} \to \mt{Type} \\ \mt{val} \; \mt{initialize} : \mt{task\_kind} \; \mt{unit} \\ -\mt{val} \; \mt{clientLeaves} : \mt{task\_kind} \; \mt{client} +\mt{val} \; \mt{clientLeaves} : \mt{task\_kind} \; \mt{client} \\ +\mt{val} \; \mt{periodic} : \mt{int} \to \mt{task\_kind} \; \mt{unit} \end{array}$$ A task kind names a particular extension point of generated applications, where the type parameter of a task kind describes which extra input data is available at that extension point. Add task code with the special declaration form $\mt{task} \; e_1 = e_2$, where $e_1$ is a task kind with data $\tau$, and $e_2$ is a function from $\tau$ to $\mt{transaction} \; \mt{unit}$. The currently supported task kinds are: \begin{itemize} -\item $\mt{initialize}$: Code that is run in each freshly-allocated request context. +\item $\mt{initialize}$: Code that is run when the application starts up. \item $\mt{clientLeaves}$: Code that is run for each client that the runtime system decides has surfed away. When a request that generates a new client handle is aborted, that handle will still eventually be passed to $\mt{clientLeaves}$ task code, even though the corresponding browser was never informed of the client handle's existence. In other words, in general, $\mt{clientLeaves}$ handlers will be called more times than there are actual clients. +\item $\mt{periodic} \; n$: Code that is run when the application starts up and then every $n$ seconds thereafter. \end{itemize} diff --git a/include/types.h b/include/types.h index 2adda753..01776213 100644 --- a/include/types.h +++ b/include/types.h @@ -58,6 +58,11 @@ typedef void (*uw_callback)(void *); typedef void (*uw_callback_with_retry)(void *, int will_retry); typedef void (*uw_logger)(void*, const char *fmt, ...); +typedef struct { + void (*callback)(uw_context); + unsigned int period; +} uw_periodic; + typedef struct { int inputs_len, timeout; char *url_prefix; @@ -80,6 +85,8 @@ typedef struct { int (*check_mime)(const char *); void (*on_error)(uw_context, char *); + + uw_periodic *periodics; // 0-terminated array } uw_app; #define ERROR_BUF_LEN 1024 diff --git a/include/urweb.h b/include/urweb.h index a0decd11..9314d089 100644 --- a/include/urweb.h +++ b/include/urweb.h @@ -293,4 +293,6 @@ uw_Basis_int uw_Basis_rand(uw_context); extern int uw_time_max, uw_supports_direct_status, uw_min_heap; +failure_kind uw_runCallback(uw_context, void (*callback)(uw_context)); + #endif diff --git a/lib/ur/basis.urs b/lib/ur/basis.urs index 2a61b701..2a3b9a33 100644 --- a/lib/ur/basis.urs +++ b/lib/ur/basis.urs @@ -813,6 +813,7 @@ val show_xml : ctx ::: {Unit} -> use ::: {Type} -> bind ::: {Type} -> show (xml con task_kind :: Type -> Type val initialize : task_kind unit val clientLeaves : task_kind client +val periodic : int -> task_kind unit (** Information flow security *) diff --git a/src/c/request.c b/src/c/request.c index e51f95ae..b49a524e 100644 --- a/src/c/request.c +++ b/src/c/request.c @@ -79,9 +79,44 @@ static void *ticker(void *data) { return NULL; } +typedef struct { + uw_app *app; + void *logger_data; + uw_logger log_error, log_debug; +} loggers; + +typedef struct { + loggers *ls; + uw_periodic pdic; +} periodic; + +static void *periodic_loop(void *data) { + periodic *p = (periodic *)data; + uw_context ctx = uw_request_new_context(p->ls->app, p->ls->logger_data, p->ls->log_error, p->ls->log_debug); + + if (!ctx) + exit(1); + + while (1) { + failure_kind r; + do { + r = uw_runCallback(ctx, p->pdic.callback); + } while (r == UNLIMITED_RETRY); + + sleep(p->pdic.period); + }; +} + void uw_request_init(uw_app *app, void *logger_data, uw_logger log_error, uw_logger log_debug) { uw_context ctx; failure_kind fk; + uw_periodic *ps; + loggers *ls = malloc(sizeof(loggers)); + + ls->app = app; + ls->logger_data = logger_data; + ls->log_error = log_error; + ls->log_debug = log_debug; uw_global_init(); uw_app_init(app); @@ -113,6 +148,18 @@ void uw_request_init(uw_app *app, void *logger_data, uw_logger log_error, uw_log } uw_free(ctx); + + for (ps = app->periodics; ps->callback; ++ps) { + pthread_t thread; + periodic *arg = malloc(sizeof(periodic)); + arg->ls = ls; + arg->pdic = *ps; + + if (pthread_create(&thread, NULL, periodic_loop, arg)) { + fprintf(stderr, "Error creating periodic thread\n"); + exit(1); + } + } } @@ -468,12 +515,6 @@ request_result uw_request(uw_request_context rc, uw_context ctx, } } -typedef struct { - uw_app *app; - void *logger_data; - uw_logger log_error, log_debug; -} loggers; - void *client_pruner(void *data) { loggers *ls = (loggers *)data; uw_context ctx = uw_request_new_context(ls->app, ls->logger_data, ls->log_error, ls->log_debug); diff --git a/src/c/urweb.c b/src/c/urweb.c index 2b54e87c..0356e0fa 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -3492,3 +3492,19 @@ uw_Basis_postBody uw_getPostBody(uw_context ctx) { else uw_error(ctx, FATAL, "Asked for POST body when none exists"); } + +failure_kind uw_runCallback(uw_context ctx, void (*callback)(uw_context)) { + int r = setjmp(ctx->jmp_buf); + + if (ctx->app->db_begin(ctx)) + uw_error(ctx, BOUNDED_RETRY, "Error running SQL BEGIN"); + + if (r == 0) { + callback(ctx); + uw_commit(ctx); + } + else + uw_rollback(ctx, 0); + + return r; +} diff --git a/src/cjr.sml b/src/cjr.sml index c57128cf..7ea665ce 100644 --- a/src/cjr.sml +++ b/src/cjr.sml @@ -103,7 +103,7 @@ datatype exp' = withtype exp = exp' located -datatype task = Initialize | ClientLeaves +datatype task = Initialize | ClientLeaves | Periodic of Int64.int datatype decl' = DStruct of int * (string * typ) list diff --git a/src/cjr_print.sml b/src/cjr_print.sml index 2bb5775e..b4f75eb5 100644 --- a/src/cjr_print.sml +++ b/src/cjr_print.sml @@ -2796,6 +2796,7 @@ fun p_file env (ds, ps) = val initializers = List.mapPartial (fn (DTask (Initialize, x1, x2, e), _) => SOME (x1, x2, e) | _ => NONE) ds val expungers = List.mapPartial (fn (DTask (ClientLeaves, x1, x2, e), _) => SOME (x1, x2, e) | _ => NONE) ds + val periodics = List.mapPartial (fn (DTask (Periodic n, x1, x2, e), _) => SOME (n, x1, x2, e) | _ => NONE) ds val onError = ListUtil.search (fn (DOnError n, _) => SOME n | _ => NONE) ds @@ -2887,6 +2888,36 @@ fun p_file env (ds, ps) = newline, newline, + box (ListUtil.mapi (fn (i, (_, x1, x2, e)) => + box [string "static void uw_periodic", + string (Int.toString i), + string "(uw_context ctx) {", + newline, + box [string "uw_unit __uwr_", + string x1, + string "_0 = uw_unit_v, __uwr_", + string x2, + string "_1 = uw_unit_v;", + newline, + p_exp (E.pushERel (E.pushERel env x1 dummyt) x2 dummyt) e, + string ";", + newline], + string "}", + newline, + newline]) periodics), + + string "static uw_periodic my_periodics[] = {", + box (ListUtil.mapi (fn (i, (n, _, _, _)) => + box [string "{uw_periodic", + string (Int.toString i), + string ",", + space, + string (Int64.toString n), + string "},"]) periodics), + string "{NULL}};", + newline, + newline, + string "static const char begin_xhtml[] = \"\\n\\n\";", newline, newline, @@ -3043,7 +3074,7 @@ fun p_file env (ds, ps) = "uw_db_init", "uw_db_begin", "uw_db_commit", "uw_db_rollback", "uw_db_close", "uw_handle", "uw_input_num", "uw_cookie_sig", "uw_check_url", "uw_check_mime", - case onError of NONE => "NULL" | SOME _ => "uw_onError"], + case onError of NONE => "NULL" | SOME _ => "uw_onError", "my_periodics"], string "};", newline] end diff --git a/src/cjrize.sml b/src/cjrize.sml index 0505af62..9c297fad 100644 --- a/src/cjrize.sml +++ b/src/cjrize.sml @@ -667,6 +667,7 @@ fun cifyDecl ((d, loc), sm) = val tk = case #1 e1 of L.EFfi ("Basis", "initialize") => L'.Initialize | L.EFfi ("Basis", "clientLeaves") => L'.ClientLeaves + | L.EFfiApp ("Basis", "periodic", [(L.EPrim (Prim.Int n), _)]) => L'.Periodic n | _ => (ErrorMsg.errorAt loc "Task kind not fully determined"; L'.Initialize) val (e, sm) = cifyExp (e, sm) diff --git a/tests/periodic.ur b/tests/periodic.ur new file mode 100644 index 00000000..baf49b3b --- /dev/null +++ b/tests/periodic.ur @@ -0,0 +1,4 @@ +task periodic 5 = fn () => debug "Every 5 seconds" +task periodic 13 = fn () => debug "Every 13 seconds" + +fun main () : transaction page = return -- cgit v1.2.3 From d3789e023986953df75e8b6453b2a164bdc96e91 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Mon, 20 Dec 2010 08:48:20 -0500 Subject: Fix manual discussion of tuple syntax --- doc/manual.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual.tex b/doc/manual.tex index f86ea97e..da12d3c2 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -512,7 +512,7 @@ A record type may be written $\{(c = c,)^*\}$, which elaborates to $\$[(c = c,)^ The notation $[c_1, \ldots, c_n]$ is shorthand for $[c_1 = (), \ldots, c_n = ()]$. -A tuple type $(\tau_1, \ldots, \tau_n)$ expands to a record type $\{1 = \tau_1, \ldots, n = \tau_n\}$, with natural numbers as field names. A tuple pattern $(p_1, \ldots, p_n)$ expands to a rigid record pattern $\{1 = p_1, \ldots, n = p_n\}$. Positive natural numbers may be used in most places where field names would be allowed. +A tuple type $\tau_1 \times \ldots \times \tau_n$ expands to a record type $\{1 : \tau_1, \ldots, n : \tau_n\}$, with natural numbers as field names. A tuple expression $(e_1, \ldots, e_n)$ expands to a record expression $\{1 = e_1, \ldots, n = e_n\}$. A tuple pattern $(p_1, \ldots, p_n)$ expands to a rigid record pattern $\{1 = p_1, \ldots, n = p_n\}$. Positive natural numbers may be used in most places where field names would be allowed. In general, several adjacent $\lambda$ forms may be combined into one, and kind and type annotations may be omitted, in which case they are implicitly included as wildcards. More formally, for constructor-level abstractions, we can define a new non-terminal $b ::= x \mid (x :: \kappa) \mid X$ and allow composite abstractions of the form $\lambda b^+ \Rightarrow c$, elaborating into the obvious sequence of one core $\lambda$ per element of $b^+$. -- cgit v1.2.3 From 0ecaa53f8c3951d695a4379dd1b353863749963a Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Mon, 20 Dec 2010 19:28:41 -0500 Subject: Fix manual mistake about '@' notations; remove obsolete demos --- demo/more/bid.ur | 158 -------------- demo/more/bid.urs | 7 - demo/more/bulkEdit.ur | 52 ----- demo/more/bulkEdit.urs | 24 --- demo/more/checkGroup.ur | 15 -- demo/more/checkGroup.urs | 5 - demo/more/conference.ur | 459 ----------------------------------------- demo/more/conference.urp | 15 -- demo/more/conference.urs | 91 -------- demo/more/conference1.ur | 33 --- demo/more/conference1.urp | 5 - demo/more/conferenceFields.ur | 25 --- demo/more/conferenceFields.urs | 7 - demo/more/decision.ur | 74 ------- demo/more/decision.urs | 14 -- demo/more/dnat.ur | 42 ---- demo/more/dnat.urs | 8 - demo/more/expandable.ur | 23 --- demo/more/expandable.urs | 6 - demo/more/meta.ur | 91 -------- demo/more/meta.urs | 36 ---- demo/more/select.ur | 3 - demo/more/select.urs | 1 - doc/manual.tex | 2 +- 24 files changed, 1 insertion(+), 1195 deletions(-) delete mode 100644 demo/more/bid.ur delete mode 100644 demo/more/bid.urs delete mode 100644 demo/more/bulkEdit.ur delete mode 100644 demo/more/bulkEdit.urs delete mode 100644 demo/more/checkGroup.ur delete mode 100644 demo/more/checkGroup.urs delete mode 100644 demo/more/conference.ur delete mode 100644 demo/more/conference.urp delete mode 100644 demo/more/conference.urs delete mode 100644 demo/more/conference1.ur delete mode 100644 demo/more/conference1.urp delete mode 100644 demo/more/conferenceFields.ur delete mode 100644 demo/more/conferenceFields.urs delete mode 100644 demo/more/decision.ur delete mode 100644 demo/more/decision.urs delete mode 100644 demo/more/dnat.ur delete mode 100644 demo/more/dnat.urs delete mode 100644 demo/more/expandable.ur delete mode 100644 demo/more/expandable.urs delete mode 100644 demo/more/meta.ur delete mode 100644 demo/more/meta.urs delete mode 100644 demo/more/select.ur delete mode 100644 demo/more/select.urs (limited to 'doc') diff --git a/demo/more/bid.ur b/demo/more/bid.ur deleted file mode 100644 index 50645f38..00000000 --- a/demo/more/bid.ur +++ /dev/null @@ -1,158 +0,0 @@ -con fields userId paperId = [User = userId, Paper = paperId] - -functor Make(M : Conference.INPUT) = struct - open M - - table bid : {User : userId, Paper : paperId, Interest : char} - PRIMARY KEY (User, Paper) - - table assignment : {User : userId, Paper : paperId} - PRIMARY KEY (User, Paper) - - fun intOut ch = - case ch of - #"_" => "Maybe" - | #"-" => "No" - | #"+" => "Yes" - | _ => error Bid: Invalid Interest code - - val linksForChair = - let - fun assignPapers () = - tup <- query (SELECT paper.Id, paper.{{M.paper}}, user.Id, user.Nam, bid.Interest, assignment.User - FROM paper JOIN bid ON bid.Paper = paper.Id - JOIN user ON bid.User = user.Id - LEFT JOIN assignment ON assignment.Paper = paper.Id AND assignment.User = user.Id - ORDER BY paper.Id, bid.Interest, user.Nam) - (fn r (paper, int, acc, ints, papers) => - if (case paper of None => False | Some r' => r'.Id = r.Paper.Id) then - if int = r.Bid.Interest then - return (paper, int, (r.User.Id, r.User.Nam, Option.isSome r.Assignment.User) :: acc, - ints, papers) - else - return (paper, r.Bid.Interest, (r.User.Id, r.User.Nam, - Option.isSome r.Assignment.User) :: [], - (int, acc) :: ints, papers) - else - return (Some r.Paper, r.Bid.Interest, - (r.User.Id, r.User.Nam, Option.isSome r.Assignment.User) :: [], [], - case paper of - None => papers - | Some r => (r.Id, r -- #Id, (int, acc) :: ints) :: papers)) - (None, #" ", [], [], []); - let - val papersL = case tup.1 of - Some r => (r.Id, r -- #Id, (tup.2, tup.3) :: tup.4) :: tup.5 - | None => [] - - fun makePapers () = List.mapM (fn (pid, extra, ints) => - ints <- List.mapM (fn (int, users) => - cg <- CheckGroup.create - (List.mp - (fn (id, nam, sel) => - (id, txt nam, sel)) - users); - ex <- Expandable.create - (CheckGroup.render cg); - return (int, cg, ex)) ints; - return (pid, extra, ints)) papersL - - fun saveAssignment ls = - dml (DELETE FROM assignment WHERE TRUE); - List.app (fn (pid, uids) => - List.app (fn uid => dml (INSERT INTO assignment (Paper, User) - VALUES ({[pid]}, {[uid]}))) uids) ls - in - papers <- source []; - - return -

    Assign papers

    - - -
    - #{[pid]}: {summarizePaper extra}: - - this <- CheckGroup.selected cg; - total <- total; - return (List.length this + total)) (return 0) ints; - return (txt n)}/>
    - - {List.mapX (fn (int, _, ex) => - {[intOut int]}: {Expandable.render ex} - ) ints} -
    ) papers)}/> - -
    -
    - - - ); - return -

    Bid on papers

    - -
    -
    Id:
    {[m.Nam]}: {m.Widget s}
    {[fs.T.Id]}{col.Show v}{col.Show v} [Update] @@ -67,10 +66,9 @@ functor Make(M : sig
    ID{cdata col.Nam}{cdata col.Nam}
    } tag is $[\mt{Body}, \mt{Tr}]$, to indicate a kind of nesting inside \texttt{} and \texttt{
    {useMore (summarizePaper (r.Paper -- #Id))} - - - {useMore (Select.selectChar ((#"-", "No") :: (#"_", "Maybe") :: (#"+", "Yes") :: []) - r.Bid.Interest)} -
    - - {ps} -
    Paper Your Bid
    - - -
    - - and changeBids r = - me <- getPcLogin; - List.app (fn {Paper = p, Bid = b} => - case b of - "" => return () - | _ => let - val p = readError p - in - (dml (DELETE FROM bid WHERE Paper = {[p]} AND User = {[me.Id]}); - dml (INSERT INTO bid (Paper, User, Interest) - VALUES ({[p]}, {[me.Id]}, {[String.sub b 0]}))) - end) r.Papers; - yourBids () - in - -
  • Bid on papers
  • -
    - end - - con yourPaperTables = [Assignment = _] - constraint [Paper] ~ yourPaperTables - fun joinYourPaper [tabs] [paper] [[Paper] ~ tabs] [[Paper] ~ _] [tabs ~ yourPaperTables] [[Id] ~ paper] - (uid : userId) (fi : sql_from_items ([Paper = [Id = paperId] ++ paper] ++ tabs)) = - sql_inner_join fi (sql_from_table [#Assignment] assignment) - (WHERE Paper.Id = Assignment.Paper AND Assignment.User = {[uid]}) -end diff --git a/demo/more/bid.urs b/demo/more/bid.urs deleted file mode 100644 index 1504c1b7..00000000 --- a/demo/more/bid.urs +++ /dev/null @@ -1,7 +0,0 @@ -con fields :: Type -> Type -> {Type} - -functor Make (M : Conference.INPUT) : Conference.OUTPUT where con paper = M.paper - where con userId = M.userId - where con paperId = M.paperId - where con yourPaperTables = [Assignment - = fields M.userId M.paperId] diff --git a/demo/more/bulkEdit.ur b/demo/more/bulkEdit.ur deleted file mode 100644 index 0226c3dd..00000000 --- a/demo/more/bulkEdit.ur +++ /dev/null @@ -1,52 +0,0 @@ -open Meta - -functor Make(M : sig - con keyName :: Name - con keyType :: Type - val showKey : show keyType - val readKey : read keyType - val injKey : sql_injectable keyType - - con visible :: {(Type * Type)} - constraint [keyName] ~ visible - val folder : folder visible - val visible : $(map Meta.meta visible) - - con invisible :: {Type} - constraint [keyName] ~ invisible - constraint visible ~ invisible - - val title : string - val isAllowed : transaction bool - table t : ([keyName = keyType] ++ map fst visible ++ invisible) - end) = struct - - open M - - fun main () = - items <- queryX (SELECT t.{keyName}, t.{{map fst visible}} FROM t) - (fn r => - - {useMore (allPopulatedTr visible (r.T -- keyName) folder)} - ); - - return -

    {[title]}

    - -
    - {foldRX [meta] [_] - (fn [nm :: Name] [p :: (Type * Type)] [rest :: {(Type * Type)}] [[nm] ~ rest] m => - ) [_] folder visible} - {items} - -
    {[m.Nam]}
    -
    - - and save r = - List.app (fn user => dml (update [map fst visible] ! - (ensql visible (user -- keyName) folder) - t - (WHERE t.{keyName} = {[readError user.keyName]}))) r.Users; - main () - -end diff --git a/demo/more/bulkEdit.urs b/demo/more/bulkEdit.urs deleted file mode 100644 index 0e5d7a6c..00000000 --- a/demo/more/bulkEdit.urs +++ /dev/null @@ -1,24 +0,0 @@ -functor Make(M : sig - con keyName :: Name - con keyType :: Type - val showKey : show keyType - val readKey : read keyType - val injKey : sql_injectable keyType - - con visible :: {(Type * Type)} - constraint [keyName] ~ visible - val folder : folder visible - val visible : $(map Meta.meta visible) - - con invisible :: {Type} - constraint [keyName] ~ invisible - constraint visible ~ invisible - - val title : string - val isAllowed : transaction bool - table t : ([keyName = keyType] ++ map fst visible ++ invisible) - end) : sig - - val main : unit -> transaction page - -end diff --git a/demo/more/checkGroup.ur b/demo/more/checkGroup.ur deleted file mode 100644 index ab1cc781..00000000 --- a/demo/more/checkGroup.ur +++ /dev/null @@ -1,15 +0,0 @@ -con t ctx data = list (data * xml ctx [] [] * source bool) - -fun create [ctx] [data] (items : list (data * xml ctx [] [] * bool)) = - List.mapM (fn (d, x, b) => s <- source b; return (d, x, s)) items - -fun render [ctx] [data] [[Body] ~ ctx] (t : t ([Body] ++ ctx) data) = - List.mapX (fn (_, x, s) => {x}
    ) t - -fun selected [ctx] [data] (t : t ctx data) = - List.foldlM (fn (d, _, s) ls => - s <- signal s; - return (if s then - d :: ls - else - ls)) [] t diff --git a/demo/more/checkGroup.urs b/demo/more/checkGroup.urs deleted file mode 100644 index d448527e..00000000 --- a/demo/more/checkGroup.urs +++ /dev/null @@ -1,5 +0,0 @@ -con t :: {Unit} -> Type -> Type - -val create : ctx ::: {Unit} -> data ::: Type -> list (data * xml ctx [] [] * bool) -> transaction (t ctx data) -val render : ctx ::: {Unit} -> data ::: Type -> [[Body] ~ ctx] => t ([Body] ++ ctx) data -> xml ([Body] ++ ctx) [] [] -val selected : ctx ::: {Unit} -> data ::: Type -> t ctx data -> signal (list data) diff --git a/demo/more/conference.ur b/demo/more/conference.ur deleted file mode 100644 index bf8e364a..00000000 --- a/demo/more/conference.ur +++ /dev/null @@ -1,459 +0,0 @@ -signature INPUT = sig - con paper :: {Type} - constraint [Id, Document] ~ paper - - type userId - val userId_inj : sql_injectable_prim userId - table user : {Id : userId, Nam : string, Password : string, Chair : bool, OnPc : bool} - PRIMARY KEY Id, - CONSTRAINT Nam UNIQUE Nam - - type paperId - val paperId_inj : sql_injectable_prim paperId - val paperId_show : show paperId - val paperId_read : read paperId - val paperId_eq : eq paperId - table paper : ([Id = paperId, Document = blob] ++ paper) - PRIMARY KEY Id - - con review :: {Type} - constraint [Paper, User] ~ review - table review : ([Paper = paperId, User = userId] ++ review) - PRIMARY KEY (Paper, User) - - val checkLogin : transaction (option {Id : userId, Nam : string, Chair : bool, OnPc : bool}) - val getLogin : transaction {Id : userId, Nam : string, Chair : bool, OnPc : bool} - val getPcLogin : transaction {Id : userId, Nam : string, Chair : bool} - val checkChair : transaction unit - val summarizePaper : ctx ::: {Unit} -> [[Body] ~ ctx] => $paper -> xml ([Body] ++ ctx) [] [] -end - -signature OUTPUT = sig - con paper :: {Type} - type userId - type paperId - - val linksForPc : xbody - val linksForChair : xbody - - con yourPaperTables :: {{Type}} - constraint [Paper] ~ yourPaperTables - val joinYourPaper : tabs ::: {{Type}} -> paper ::: {Type} - -> [[Paper] ~ tabs] => [[Paper] ~ yourPaperTables] => [tabs ~ yourPaperTables] => [[Id] ~ paper] => - userId - -> sql_from_items ([Paper = [Id = paperId] ++ paper] ++ tabs) - -> sql_from_items (yourPaperTables ++ [Paper = [Id = paperId] ++ paper] ++ tabs) -end - -open Meta - -functor Make(M : sig - con paper :: {(Type * Type)} - constraint [Id, Document, Authors] ~ paper - val paper : $(map Meta.meta paper) - val paperFolder : folder paper - - con paperPrivate :: {Type} - constraint [Id, Document, Authors] ~ paperPrivate - constraint paper ~ paperPrivate - val paperPrivate : $(map Meta.private paperPrivate) - val paperPrivateFolder : folder paperPrivate - - con review :: {(Type * Type)} - constraint [Paper, User] ~ review - val review : $(map Meta.meta review) - val reviewFolder : folder review - - val submissionDeadline : time - val summarizePaper : ctx ::: {Unit} -> [[Body] ~ ctx] => $(map fst paper ++ paperPrivate) - -> xml ([Body] ++ ctx) [] [] - - functor Make (M : INPUT where con paper = map fst paper ++ paperPrivate - where con review = map fst review) - : OUTPUT where con paper = map fst paper ++ paperPrivate - where con userId = M.userId - where con paperId = M.paperId - end) = struct - - table user : {Id : int, Nam : string, Password : string, Chair : bool, OnPc : bool} - PRIMARY KEY Id, - CONSTRAINT Nam UNIQUE Nam - sequence userId - - con paper = [Id = int, Document = blob] ++ map fst M.paper ++ M.paperPrivate - table paper : paper - PRIMARY KEY Id - sequence paperId - - table authorship : {Paper : int, User : int} - PRIMARY KEY (Paper, User), - CONSTRAINT Paper FOREIGN KEY Paper REFERENCES paper(Id) ON DELETE CASCADE, - CONSTRAINT User FOREIGN KEY User REFERENCES user(Id) - - con review = [Paper = int, User = int] ++ map fst M.review - table review : review - PRIMARY KEY (Paper, User), - CONSTRAINT Paper FOREIGN KEY Paper REFERENCES paper(Id), - CONSTRAINT User FOREIGN KEY User REFERENCES user(Id) - sequence reviewId - - cookie login : {Id : int, Password : string} - - val checkLogin = - r <- getCookie login; - case r of - None => return None - | Some r => - oneOrNoRows1 (SELECT user.Id, user.Nam, user.Chair, user.OnPc - FROM user - WHERE user.Id = {[r.Id]} - AND user.Password = {[r.Password]}) - - val getLogin = - ro <- checkLogin; - case ro of - None => error You must be logged in to do that. - | Some r => return r - - val getPcLogin = - r <- getLogin; - if r.OnPc then - return (r -- #OnPc) - else - error You are not on the PC. - - val checkChair = - r <- getLogin; - if r.Chair then - return () - else - error You are not a chair. - - structure O = M.Make(struct - val user = user - val paper = paper - val review = review - val checkLogin = checkLogin - val getLogin = getLogin - val getPcLogin = getPcLogin - val checkChair = checkChair - val summarizePaper = @@M.summarizePaper - end) - - val checkOnPc = - r <- getLogin; - if r.OnPc then - return () - else - error You aren't authorized to do that. - - fun checkPaper id = - r <- getLogin; - if r.OnPc then - return () - else - error You aren't authorized to see that paper. - - structure Users = BulkEdit.Make(struct - con keyName = #Id - val visible = {Nam = string "Name", - Chair = bool "Chair?", - OnPc = bool "On PC?"} - - val title = "Users" - val isAllowed = - me <- checkLogin; - return (Option.isSome me) - - val t = user - end) - - fun doRegister r = - n <- oneRowE1 (SELECT COUNT( * ) AS N - FROM user - WHERE user.Nam = {[r.Nam]}); - if n > 0 then - register (Some "Sorry; that username is taken.") - else - id <- nextval userId; - dml (INSERT INTO user (Id, Nam, Password, Chair, OnPc) - VALUES ({[id]}, {[r.Nam]}, {[r.Password]}, FALSE, FALSE)); - setCookie login {Id = id, Password = r.Password}; - main () - - and register msg = return -

    Registering a New Account

    - - {case msg of - None => - | Some msg =>
    {[msg]}
    } - -
    - - - -
    Username:
    Password:
    -
    - - and signin r = - ro <- oneOrNoRowsE1 (SELECT user.Id AS N - FROM user - WHERE user.Nam = {[r.Nam]} - AND user.Password = {[r.Password]}); - (case ro of - None => return () - | Some id => setCookie login {Id = id, Password = r.Password}); - m <- main' (); - return - {case ro of - None =>
    Invalid username or password.
    - | _ => } - - {m} - - - and main' () = - me <- checkLogin; - now <- now; - return
      - {case me of - None => -
    • Register for access
    • -
    • Log in:
      - - - -
      Username:
      Password:
    • -
      - | Some me => -
      Welcome, {[me.Nam]}!
      - - {if me.Chair then - -
    • Manage users
    • - {O.linksForChair} -
      - else - } - - {if me.OnPc then - -
    • All papers
    • -
    • Your papers
    • - {O.linksForPc} -
      - else - } - - {if now < M.submissionDeadline then -
    • Submit
    • - else - } - } -
    - - and main () = - m <- main' (); - return {m} - - and submit () = - let - fun doSubmit r = - me <- getLogin; - coauthors <- List.mapM (fn name => oneOrNoRowsE1 (SELECT user.Id AS N - FROM user - WHERE user.Nam = {[name.Nam]})) r.Authors; - if List.exists Option.isNone coauthors then - error At least one of those coauthor usernames isn't registered. - else - id <- nextval paperId; - dml (insert paper ({Id = sql_inject id, Document = sql_inject (fileData r.Document)} - ++ ensql M.paper (r -- #Authors -- #Document) M.paperFolder - ++ initialize M.paperPrivate M.paperPrivateFolder)); - List.app (fn uid => - case uid of - None => error Impossible empty uid! - | Some uid => dml (INSERT INTO authorship (Paper, User) - VALUES ({[id]}, {[uid]}))) - (Some me.Id :: coauthors); - return - Thanks for submitting! - - in - me <- getLogin; - numAuthors <- Dnat.zero; - - return -

    Submit a Paper

    - -
    - Author: {[me.Nam]}
    - - {Dnat.render Author:
    numAuthors} - -