summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--demo/prose84
-rw-r--r--src/c/http.c59
-rw-r--r--src/compiler.sml2
-rw-r--r--src/urweb.grm20
-rw-r--r--tests/qualrecord.ur7
5 files changed, 122 insertions, 50 deletions
diff --git a/demo/prose b/demo/prose
index 8637ef68..781eeed5 100644
--- a/demo/prose
+++ b/demo/prose
@@ -1,38 +1,88 @@
-<p><b>Ur/Web</b> 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). <b>Ur</b> 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 <a href="http://sml.sourceforge.net/">Standard ML</a>, with a few <a href="http://www.haskell.org/">Haskell</a>-isms added, and kinder, gentler versions added of many features from dependently-typed languages like the logic behind <a href="http://coq.inria.fr/">Coq</a>. 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.</p>
+<p><b>Ur/Web</b> 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). <b>Ur</b> 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 <a href="http://sml.sourceforge.net/">Standard ML</a>, with a few <a href="http://www.haskell.org/">Haskell</a>-isms added, and kinder, gentler versions added of many features from dependently typed languages like the logic behind <a href="http://coq.inria.fr/">Coq</a>. 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.</p>
-<p>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 <a href="http://www.impredicative.com/ur/manual.pdf">the manual</a> for more detailed instructions.
+<p>The page you are currently reading is a part of the demo included with the Ur/Web sources and supporting files available from <a href="https://github.com/urweb/urweb">GitHub</a>. The following steps will build a local instance of the demo if you're lucky (and running a Debian-based Linux OS, which actually tend to have Ur/Web packages built in these days). If you're not lucky, you can consult the beginning of <a href="http://www.impredicative.com/ur/manual.pdf">the manual</a> for more detailed instructions.</p>
-<blockquote><pre>./configure
+<h6>Install System Dependencies</h6>
+
+<p>
+<blockquote><pre>sudo apt-get install build-essential \
+ emacs-goodies-el \
+ libgmp-dev \
+ libssl-dev \
+ libpq-dev \
+ libsqlite3-dev \
+ mlton \
+ sqlite3</blockquote></pre></p>
+
+<h6>Build and Install the Ur/Web Framework</h6>
+
+<p><blockquote><pre>./configure
make
sudo make install
-urweb -noEmacs -demo /Demo demo</pre></blockquote></p>
+</pre></blockquote></p>
-<p>The <tt>-demo /Demo</tt> flag says that we want to build a demo application that expects its URIs to begin with <tt>/Demo</tt>. The final argument <tt>demo</tt> gives the path to a directory housing demo files. One of the files in that directory is <tt>prose</tt>, a file describing the different demo pieces with HTML. Some lines of <tt>prose</tt> have the form <tt><i>foo</i>.urp</tt>, naming particular project files (with the extension <tt>.urp</tt>) in that directory.</p>
+<h6>Compile the Demo the Easy Way</h6>
-<p>These project files can also be built separately. For example, you could run
+<p><blockquote><pre>$ urweb -dbms sqlite -db /path_to_db.sqlite -demo /Demo demo
+</blockquote></pre></p>
+
+<p>The <tt>-dbms sqlite</tt> flag indicates that instead of using the default database management system (<a href="https://www.postgresql.org/">PostgreSQL</a>), we wish to use <a href="https://sqlite.org/">SQLite</a> (usually unsuited for production). The <tt>-db</tt> flag allows us to specify the file-system path to our SQLite database. The <tt>-demo /Demo</tt> parameter indicates that we want to build a demo application that expects its URIs to begin with <tt>/Demo</tt>. The final argument <tt>demo</tt> gives the path to a directory housing Ur/Web source files (<tt>.ur</tt>, <tt>.urp</tt>, <tt>.urs</tt>, etc.).
+</p>
+
+<p>
+The following files are created during the compilation process:
+<ul>
+<li><tt>demo/demo.exe</tt>
+<li><tt>demo/out/*</tt>
+<li><tt>demo/demo.sql</tt>
+</ul>
+</p>
+
+<h6>Initialize the Database</h6>
-<blockquote><pre>urweb demo/hello</pre></blockquote>
+<p>
+When we compiled the demo in the last step, a <tt>demo.sql</tt> file was created for us, which contains all the information required to create a database compatible with the demo web app. The command below will provision our SQLite database. To see an example of where a database table is defined in source code, check out <tt>demo/crud1.ur</tt>. Also of interest is the file <tt>demo.urp</tt>, which contains a <tt>database</tt> directive with the PostgreSQL database that the demo web server will try to connect to if database information isn't provided as command-line arguments when the application is compiled.
+
+<blockquote><pre>$ sqlite3 /path/to/database/file &lt;demo/demo.sql
+</blockquote></pre>
+</p>
+
+<h6>Boot the App</h6>
+
+Executing the binary generated above (<tt>demo/demo.exe</tt>) with no arguments will start a single-threaded server listening on port 8080. (To answer the usual first question: the <tt>.exe</tt> prefix has nothing to do with Windows and does not mean that you compiled for the wrong OS!) Pass the flag <tt>-h</tt> to see which options are available on such freshly built binaries.
+</p>
+<p><blockquote><pre>$ demo/demo.exe
+Database connection initialized.
+Listening on port 8080....
+</blockquote></pre>
+Test out <tt>http://localhost:8080/Demo/Demo/main</tt>, which should consist of links to the individual demos after booting the app.</p>
+</p>
-to build the "Hello World" demo application. Whether building the pieces separately or all at once with the <tt>-demo</tt> flag, a standalone web server executable is generated. The <tt>-demo</tt> command line will generate <tt>demo/demo.exe</tt>, and the other command line will generate <tt>demo/hello.exe</tt>. Either can be run with no arguments to start a single-threaded server accepting requests on port 8080. Pass the flag <tt>-h</tt> to see which options are available.</p>
+<h6>Serve the Static Content with a Reverse Proxy</h6>
-<p>The <tt>-demo</tt> version also generates some HTML in a subdirectory <tt>out</tt> of the demo directory. It is easy to set Apache up to serve these HTML files, and to proxy out to the Ur/Web web server for dynamic page requests. This configuration works for me, where <tt>DIR</tt> is the location of an Ur/Web source distribution.
+<p>The <tt>-demo</tt> version also generates some HTML in a subdirectory <tt>out</tt> of the demo directory (e.g. <tt>index.html</tt>). It is easy to set Apache up to serve these HTML files and to proxy out to the Ur/Web web server for dynamic page requests. This configuration works for me, where <tt>DIR</tt> is the location of an Ur/Web source distribution. (You may also need to enable the proxy module with a command like <tt>a2enmod proxy_http</tt>.)
<blockquote><pre>Alias /demo/ "DIR/demo/out/"
ProxyPass /Demo/ http://localhost:8080/Demo/
ProxyPassReverse /Demo/ http://localhost:8080/Demo/</pre></blockquote></p>
-<p>Building the demo also generates a <tt>demo.sql</tt> file, giving the SQL commands to run to define all of the tables and sequences that the applications expect to see. The file <tt>demo.urp</tt> contains a <tt>database</tt> line with the PostgreSQL database that the demo web server will try to connect to.</p>
+<h6>Compile Individually</h6>
+
+<p>These project files can also be built separately. For example, you could run
+
+<blockquote><pre>$ urweb demo/hello
+</pre></blockquote>
-<p>The easiest way to get a demo running locally is probably with this alternate command sequence:
+to build the "Hello World" demo application. Doing so will invite Ur/Web to seek out the various <tt>demo/hello.*</tt> files and, from them, build a binary <tt>demo/hello.exe</tt>. The URL to access the resulting app will be <tt>http://localhost:8080/Hello/main</tt>.
+</p>
-<blockquote><pre>urweb -dbms sqlite -db /path/to/database/file -demo /Demo demo
-sqlite3 /path/to/database/file &lt;demo/demo.sql
-demo/demo.exe</pre></blockquote></p>
+<h6>This File</h6>
+<p>One of the files in the demo directory is named <tt>prose</tt>, a file describing the different demo pieces with HTML. Some lines of <tt>prose</tt> have the form <tt><i>foo</i>.urp</tt>, naming particular project files (with the extension <tt>.urp</tt>) in that directory. These make up the different pages of the tutorial.</p>
-<p>Then you can skip the static content and connect directly to the demo server at <tt>http://localhost:8080/Demo/Demo/main</tt>, which contains links to the individual demos. If you're running the server created just for <tt>hello</tt>, then the URL will be <tt>http://localhost:8080/Hello/main</tt>.</p>
+<h6>Finally, the Demos!</h6>
-<p>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.</p>
+<p>The rest of the demo focuses on introducing Ur/Web programming, one feature at a time. 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.</p>
hello.urp
@@ -118,7 +168,7 @@ ref.urp
<p>This example shows how to mix the module system with SQL to implement a kind of "abstract data type." The functor <tt>RefFun.Make</tt> takes in a type belonging to the type class of those types that may be included in SQL. The functor output includes an abstract type <tt>ref</tt>, along with operations for working with <tt>ref</tt>s via transactions. In the functor implementation, we see that <tt>ref</tt> is implemented as <tt>int</tt>, treated as primary keys of a SQL table.</p>
-<p>The functor creates a new encapsulated SQL sequence and table on each call. These local relations show up in the automatically-generated SQL file that should be run to prepare the database for use, but they are invisible from client code. We could change the functor to create different SQL relations, without needing to change client code.</p>
+<p>The functor creates a new encapsulated SQL sequence and table on each call. These local relations show up in the automatically generated SQL file that should be run to prepare the database for use, but they are invisible from client code. We could change the functor to create different SQL relations, without needing to change client code.</p>
<p>Note that, in <tt>ref.ur</tt>, the <tt>inj</tt> components of functor arguments are omitted. Since these arguments are type class witnesses, the compiler infers them automatically based on the choices of <tt>data</tt>.</p>
diff --git a/src/c/http.c b/src/c/http.c
index d186e209..1bc58677 100644
--- a/src/c/http.c
+++ b/src/c/http.c
@@ -322,19 +322,28 @@ static void sigint(int signum) {
exit(0);
}
+union uw_sockaddr {
+ struct sockaddr sa;
+ struct sockaddr_in ipv4;
+ struct sockaddr_in6 ipv6;
+};
+
int main(int argc, char *argv[]) {
// The skeleton for this function comes from Beej's sockets tutorial.
int sockfd; // listen on sock_fd
- struct sockaddr_in6 my_addr;
- struct sockaddr_in6 their_addr; // connector's address information
- socklen_t sin_size;
- int yes = 1, no = 0, uw_port = 8080, nthreads = 1, i, *names, opt;
+ union uw_sockaddr my_addr;
+ union uw_sockaddr their_addr; // connector's address information
+ socklen_t my_size = 0, sin_size;
+ int yes = 1, uw_port = 8080, nthreads = 1, i, *names, opt;
int recv_timeout_sec = 5;
signal(SIGINT, sigint);
signal(SIGPIPE, SIG_IGN);
- my_addr.sin6_addr = in6addr_any; // auto-fill with my IP
+ // default if not specified: IPv4 with my IP
+ memset(&my_addr, 0, sizeof my_addr);
+ my_addr.sa.sa_family = AF_INET;
+ my_addr.ipv4.sin_addr.s_addr = INADDR_ANY; // auto-fill with my IP
while ((opt = getopt(argc, argv, "hp:a:A:t:kqT:")) != -1) {
switch (opt) {
@@ -357,20 +366,17 @@ int main(int argc, char *argv[]) {
break;
case 'a':
- {
- char *buf = alloca(strlen(optarg) + 8);
- strcpy(buf, "::FFFF:");
- strcpy(buf + 7, optarg);
- if (!inet_pton(AF_INET6, buf, &my_addr.sin6_addr)) {
- fprintf(stderr, "Invalid IPv4 address\n");
- help(argv[0]);
- return 1;
- }
+ my_addr.sa.sa_family = AF_INET;
+ if (!inet_pton(AF_INET, optarg, &my_addr.ipv4.sin_addr)) {
+ fprintf(stderr, "Invalid IPv4 address\n");
+ help(argv[0]);
+ return 1;
}
break;
case 'A':
- if (!inet_pton(AF_INET6, optarg, &my_addr.sin6_addr)) {
+ my_addr.sa.sa_family = AF_INET6;
+ if (!inet_pton(AF_INET6, optarg, &my_addr.ipv6.sin6_addr)) {
fprintf(stderr, "Invalid IPv6 address\n");
help(argv[0]);
return 1;
@@ -413,7 +419,7 @@ int main(int argc, char *argv[]) {
names = calloc(nthreads, sizeof(int));
- sockfd = socket(AF_INET6, SOCK_STREAM, 0); // do some error checking!
+ sockfd = socket(my_addr.sa.sa_family, SOCK_STREAM, 0); // do some error checking!
if (sockfd < 0) {
fprintf(stderr, "Listener socket creation failed\n");
@@ -425,15 +431,20 @@ int main(int argc, char *argv[]) {
return 1;
}
- if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &no, sizeof(int)) < 0) {
- fprintf(stderr, "Listener IPV6_V6ONLY option resetting failed\n");
- return 1;
+ switch (my_addr.sa.sa_family)
+ {
+ case AF_INET:
+ my_size = sizeof(my_addr.ipv4);
+ my_addr.ipv4.sin_port = htons(uw_port);
+ break;
+
+ case AF_INET6:
+ my_size = sizeof(my_addr.ipv6);
+ my_addr.ipv6.sin6_port = htons(uw_port);
+ break;
}
- my_addr.sin6_family = AF_INET6; // host byte order
- my_addr.sin6_port = htons(uw_port); // short, network byte order
-
- if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof my_addr) < 0) {
+ if (bind(sockfd, &my_addr.sa, my_size) < 0) {
fprintf(stderr, "Listener socket bind failed\n");
return 1;
}
@@ -470,7 +481,7 @@ int main(int argc, char *argv[]) {
}
while (1) {
- int new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
+ int new_fd = accept(sockfd, &their_addr.sa, &sin_size);
if (new_fd < 0) {
qfprintf(stderr, "Socket accept failed\n");
diff --git a/src/compiler.sml b/src/compiler.sml
index 4fe2dfd5..481f04b6 100644
--- a/src/compiler.sml
+++ b/src/compiler.sml
@@ -687,7 +687,7 @@ fun parseUrp' accLibs fname =
}
in
if accLibs then
- foldr (fn (job', job) => merge (job, job')) job (!libs)
+ foldl (fn (job', job) => merge (job, job')) job (!libs)
else
job
end
diff --git a/src/urweb.grm b/src/urweb.grm
index 40101056..c1ee74f2 100644
--- a/src/urweb.grm
+++ b/src/urweb.grm
@@ -475,6 +475,7 @@ fun patternOut (e : exp) =
| eterm of exp
| etuple of exp list
| rexp of (con * exp) list * bool
+ | rpath of con
| xml of exp
| xmlOne of exp
| xmlOpt of exp
@@ -1151,15 +1152,15 @@ ctuple : capps STAR capps ([capps1, capps2])
| capps STAR ctuple (capps :: ctuple)
rcon : ([])
- | ident EQ cexp ([(ident, cexp)])
- | ident EQ cexp COMMA rcon ((ident, cexp) :: rcon)
+ | rpath EQ cexp ([(rpath, cexp)])
+ | rpath EQ cexp COMMA rcon ((rpath, cexp) :: rcon)
-rconn : ident ([(ident, (CUnit, s (identleft, identright)))])
- | ident COMMA rconn ((ident, (CUnit, s (identleft, identright))) :: rconn)
+rconn : rpath ([(rpath, (CUnit, s (rpathleft, rpathright)))])
+ | rpath COMMA rconn ((rpath, (CUnit, s (rpathleft, rpathright))) :: rconn)
rcone : ([])
- | ident COLON cexp ([(ident, cexp)])
- | ident COLON cexp COMMA rcone ((ident, cexp) :: rcone)
+ | rpath COLON cexp ([(rpath, cexp)])
+ | rpath COLON cexp COMMA rcone ((rpath, cexp) :: rcone)
ident : CSYMBOL (CName CSYMBOL, s (CSYMBOLleft, CSYMBOLright))
| INT (CName (Int64.toString INT), s (INTleft, INTright))
@@ -1567,8 +1568,11 @@ ptuple : pat COMMA pat ([pat1, pat2])
| pat COMMA ptuple (pat :: ptuple)
rexp : DOTDOTDOT ([], true)
- | ident EQ eexp ([(ident, eexp)], false)
- | ident EQ eexp COMMA rexp ((ident, eexp) :: #1 rexp, #2 rexp)
+ | rpath EQ eexp ([(rpath, eexp)], false)
+ | rpath EQ eexp COMMA rexp ((rpath, eexp) :: #1 rexp, #2 rexp)
+
+rpath : path (CVar path, s (pathleft, pathright))
+ | CSYMBOL (CName CSYMBOL, s (CSYMBOLleft, CSYMBOLright))
xml : xmlOne xml (let
val pos = s (xmlOneleft, xmlright)
diff --git a/tests/qualrecord.ur b/tests/qualrecord.ur
new file mode 100644
index 00000000..4db64e5f
--- /dev/null
+++ b/tests/qualrecord.ur
@@ -0,0 +1,7 @@
+structure M = struct
+ con the_best_name = #Wiggles
+ con the_runner_up = #Beppo
+end
+
+val x : {M.the_best_name : int, A : int, M.the_runner_up : int} =
+ {M.the_best_name = 8, A = 9, M.the_runner_up = 10}