Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>Web Prolog Tutorial</title> | |
| <meta name="description" content=""> | |
| <meta name="author" content="Torbjörn Lager"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <!-- CSS --> | |
| <!-- | |
| <link rel="stylesheet" href="../../www/vendor/bootstrap/css/bootstrap.min.css"> | |
| <style> | |
| #tutorial { | |
| margin: 50px; | |
| padding: 16px; | |
| display: block; | |
| } | |
| .collapse { | |
| display: block; | |
| } | |
| </style> | |
| --> | |
| <style> | |
| h1 {font-size: 36px; color:black;} | |
| h2 {font-size: 28px; margin-top:48px; color:navy;} | |
| h3 {font-size: 20px; margin-top:30px; color:maroon;} | |
| em { | |
| font-weight: bold; | |
| font-style: normal; | |
| } | |
| pre { | |
| font-size: 12px; | |
| } | |
| blockquote p { | |
| font-size: 12px; | |
| } | |
| blockquote { | |
| margin-top: 12px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="tutorial"> | |
| <div> | |
| <h1>Web Prolog – a tutorial</h1> | |
| </div> | |
| <h2>Introduction</h2> | |
| <p class="lead"> | |
| Imagine a dialect of <em>Prolog</em> with processes and mailboxes and send and receive – all the means necessary for powerful concurrent and distributed programming. Alternatively, think of it as a dialect of <em>Erlang</em> with logic variables, backtracking search and a built-in database of facts and rules – the means for logic programming, knowledge representation and reasoning. Also, think of it as a <em>web logic programming language</em>. This is what <em>Web Prolog</em> is all about. | |
| </p> | |
| <p> | |
| Despite of what the title suggests, this isn't a tutorial. Our purpose is not to teach an already established programming language, but to present a proposal for a <i>new</i> language. By showing simple tutorial-style examples a feel for a language can be given, and this is exactly why we choose to present it in this form. The examples are complemented by documentation of the most important Web Prolog built-in predicates and web APIs. | |
| </p> | |
| <p> | |
| To be honest, the language isn't exactly new either, but a hybrid of two rather old and mature languages – Prolog and Erlang – and of two programming models – <i>logic programming</i> and <i>actor-based programming</i>. When it comes to the <i>purpose</i> of Web Prolog, another inspiration has been JavaScript – a <i>scripting language</i> for the Web that is usually <i>embedded</i> in other programming systems and where processes must be <i>sandboxed</i> in order to allow the execution of untrusted code. Similar to JavaScript, Web Prolog is an embedded and sandboxed <i>web programming language</i>, but in contrast to JavaScript, Web Prolog wants to become a web <i>logic</i> programming language – a language for scripting the Web with logic programs. | |
| </p> | |
| <p> | |
| The Web Prolog programming language may not be to everyone's liking and we would greatly appreciate constructive criticism of our proposal. We hope that by showing it first of all to a bunch of Prolog and Erlang programmers we can avoid (at this stage) largely irrelevant negative criticism such as "it's bad because..." followed by "...it has the same syntax as Erlang" or "...it's just as impure as Prolog" or "...it's not object-oriented". We actually like the syntax since it's so close to both Prolog and Erlang (and to Prolog in particular), and we have since long come to peace with some of the Prolog impurities while others have found remedies through research in the field of logic programming. What is more, Joe Armstrong (one of the inventors of Erlang) as well as Alan Kay (the inventor of Smalltalk) seem to have suggested that Erlang <i>is</i> in fact object-oriented (a suggestion we won't follow up on here though). | |
| </p> | |
| <p> | |
| We believe that Prolog programmers will quickly realise that with Web Prolog they will be able to do things not easily done in traditional Prolog, and that Erlang programmers will find that Web Prolog offers interesting and useful capabilities that are not present in Erlang. More generally, we believe that Erlang technology might have something to contribute to Prolog, and, of course, that Prolog technology has something to contribute to Erlang (and by that we mean <i>more</i> than it has already contributed by once upon a time having inspired Erlang). Nowadays, there isn't much contact between the Prolog community and the Erlang community. In the best of worlds, the language of Web Prolog would serve to open a line of communication between (subsets of) the two communities. | |
| </p> | |
| <p> | |
| A suitable background for people taking the tutorial is familiarity with either Prolog or Erlang, and preferably with both. In the long run, we want lots of users writing lots of applications, so sooner or later we ought to ride on the popularity of JavaScript and target also front-end programmers and JavaScript aficionados with no previous exposure to Prolog or Erlang. This requires a different kind of tutorial, however, one that starts from the basics of both logic programming in the manner of Prolog and actor-based programming in the manner of Erlang. | |
| </p> | |
| <h2>Your shell, your pengine and your node</h2> | |
| <p> | |
| We start by giving a short introduction to your shell, your pengine and your node. | |
| </p> | |
| <h3>Your shell</h3> | |
| <p> | |
| If everything works as it should, the area to the right is showing what is a more or less traditional Prolog shell. Working with an interactive shell is a vital part of using dynamic programming languages such as Erlang and Prolog and is useful when testing all kinds of code and programs. Here it's used to offer you some tutorial-style hands-on experience with Web Prolog. | |
| </p> | |
| <h3>Your pengine</h3> | |
| <p> | |
| Your shell is talking to a <em>pengine</em> (which is short for <i>Prolog engine</i>). A pengine can be described in several ways: as a programming abstraction modelled on the top-level of Prolog, as a kind of agent, as an encapsulated Prolog session, as a stateful compute server for Prolog programming tasks, or as an actor. A pengine can be created and accessed from Prolog as well as from other programming languages, and if you want, you can run two or more in parallel. </p> | |
| <div class="row"> | |
| <div class="col-xs-8"> | |
| <p>The fundamental unit of computation in Web Prolog isn't the pengine, but the <em>actor</em>. An actor is a <em>process</em> with a <em>mailbox</em> which <em>receives</em> and processes <em>messages</em> individually. An actor can <em>send</em> messages to other actors, and is also capable of <em>spawning</em> new actors. We'll see examples of many kinds of actors in this tutorial. Not all of them are pengines. A pengine is merely a special <i>kind</i> of actor, and can be distinguished from other kinds of actors by the communication protocol it implements and the behaviour it offers. | |
| </p> | |
| </div> | |
| <div class="col-xs-4"> | |
| <img style="height:4cm; width:4cm" src="img/actor.png" alt=""> | |
| </div> | |
| </div> | |
| <p> | |
| Your pengine is named <code>{{pengine}}</code>. That's its short name. (It has a long name too – we'll come to that.) The pengine is all yours, you have almost total control over it. Note, however, that if you reload this page, your pengine will disappear and you will be served a new one with a different name. | |
| </p> | |
| <h3>Your node</h3> | |
| <p> | |
| Your pengine is running on a <em>node</em> running on a computer somewhere. The purpose of a node is to host pengines and other actors. The node running your pengine is identified by the URI <code>'{{host}}'</code>. This means that your pengine also has a long name, namely <code>{{pengine}}@'{{host}}'</code>.</p> | |
| <p> | |
| The node has an <i>owner</i> who is responsible for its maintenance. When we wrote that you have "<i>almost</i> total control" of your pengine, we just meant that the owner of the node on which the pengine is running always has the ultimate say when it comes to how much resources it will be allowed to allocate. | |
| </p> | |
| <p> | |
| The node isn't only yours. Other programmers may be talking to other pengines running on the same node. They are completely shielded from each other, unless of course the programs they are running have been written to allow them to communicate. In any case, pengines don't share memory, so to share information they must exchange messages. | |
| </p> | |
| <p> | |
| At this point in time this node may well be the only one running. But we ask you to imagine a future when there are many such nodes around. In other words, we ask you to imagine a <em>Prolog Web</em> – an open and decentralised network of nodes populated by pengines and other actors running Web Prolog. The architecture of the Prolog Web serves to maintain the open, globally accessible nature of the Web, and therefore utilises Prolog-specific application protocols and representations on top of communication protocols such as HTTP and WebSocket, and web formats such as JSON. | |
| </p> | |
| <div class="alert alert-info"> | |
| <p> | |
| <strong>We are writing a book about Web Prolog</strong> with the provisional title <i>Web Prolog and the programmable Prolog Web</i> and the not-so-modest subtitle <i>An attempt to revive and rebrand Prolog</i>. In addition to a presentation of the language of Web Prolog and a description of our approach to web logic programming, it discusses the notion of a Prolog Web, compares it with the Semantic Web, and suggests that Web Prolog has properties that makes it a good candidate for a semantic web programming language. It furthermore discusses the future of Prolog and the standardisation of Web Prolog in the W3C as one possible way forward. A concept of Web Prolog <i>profiles</i> is also introduced. A draft manuscript can be downloaded from <a target="_blank" href="web-prolog.pdf">here</a>. | |
| </p> | |
| </div> | |
| <div class="alert alert-success"> | |
| <strong>Web Prolog is embedded in SWI-Prolog</strong> and uses engines, also known as interactors, for good performance. The idea behind engines is due to Paul Tarau and are implemented in SWI-Prolog by Jan Wielemaker. An engine is a Prolog virtual machine that has its own stacks and (virtual) machine state. Unlike normal Prolog threads though, they are not associated with an operating system thread which should make them scalable enough for our purpose. | |
| </div> | |
| <div class="alert alert-danger"> | |
| <p> | |
| <strong>BUG!</strong> Due to a bug in the SWI-Prolog implementation of engines, they don't deliver on this promise yet. Run the process ring example (available from the Example menu) and watch a slowdown when the ring gets big. (You'll need to download and install the node to try this as we cannot allow unauthorised users to run very many processes concurrently.) We are working on the problem and promise that it can be fixed. | |
| </p> | |
| </div> | |
| <p> | |
| Now, let's make your hands dirty and illustrate some important Web Prolog concepts by way of tutorial "exercises". They are divided into five main parts, one part showing that Web Prolog is a dialect of Prolog, one proposing that it can just as well be seen as a dialect of Erlang, and one suggesting that it's a hybrid language. Furthermore, we present the web APIs which every node is equipped with, and finally we introduce a small set of high-level predicates for making remote procedure calls. | |
| </p> | |
| <h2>Web Prolog is a dialect of Prolog</h2> | |
| <p> | |
| Prolog can be characterised as a logic programming language with imperative and procedural features added to ensure its usefulness as a general purpose programming language. Some highlights of Prolog include: built-in backtracking search, unification, logic-based knowledge representation, reasoning, a built-in dynamic database, meta-programming, and Definite Clause Grammar (DCG). These are concepts and features that Erlang doesn't support. | |
| </p> | |
| <div class="alert alert-info"> | |
| <strong>If you are an experienced Prolog programmer</strong>, you may want to skip the following four subsections where we just want to show Erlang programmers a couple of things that are easy and natural in Prolog, but not so in Erlang. | |
| </div> | |
| <h3>Running append in two different modes</h3> | |
| <p> | |
| In our first example, we're running a query calling the built-in predicate <code>append/3</code> in the deterministic mode. Clicking the Ask button below binds <code>Xs</code> to the first and only solution that this query has: | |
| </p> | |
| <pre id="append1"> | |
| ?- append([a],[b,c],Xs). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#append1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| The call and its solution are displayed in the shell to the right. | |
| </p> | |
| <div class="alert alert-info"> | |
| <strong>Want to try the same query but with different input lists? </strong> Use the up arrow key or the History menu to insert the most recent query at the prompt. Edit it to your heart's delight and hit Return when you are done. | |
| </div> | |
| <p> | |
| In our second example, we're running <code>append/3</code> in the non-deterministic mode – running it backwards (so to speak) in the way peculiar to a relational language such as Prolog. Clicking the Ask | |
| button will give you the first solution: | |
| </p> | |
| <pre id="append2"> | |
| ?- append(Xs,Ys,[a,b,c]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#append2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| To force the search for more solutions, press the semicolon (;) key on your keyboard. Hit return to stop looking for more solutions. | |
| </p> | |
| <div class="alert alert-info"> | |
| <strong>Tip!</strong> Instead of the semicolon (;) you can use the space bar. If you don't want to use your keyboard just click the Next or Stop button located in the panel below the shell. | |
| </div> | |
| <p> | |
| As those who have tried Prolog before will surely recognise, this is how a Prolog shell usually works. Normally, a Prolog shell is written in Prolog, but this shell is implemented in a combination of HTML, CSS and JavaScript and relies a lot on a JavaScript library called <a href="http://terminal.jcubic.pl">jQuery Terminal</a>. We like to think of the shell as the mediator in a form of mediated communication. You are talking to the shell using your mouse and your keyboard, and the shell is talking JSON with your pengine over a WebSocket sub-protocol. | |
| </p> | |
| <div class="alert alert-info"> | |
| <strong>Tip!</strong> By selecting Trace JSON in the panel below the shell, it's possible to inspect the JSON traffic between the shell and the pengine. This might give you some idea of how the shell works. | |
| </div> | |
| <div class="alert alert-info"> | |
| <strong>Tip!</strong> Ctrl-C can be used to abort a running computation. Or just click the Abort button. | |
| </div> | |
| <div class="alert alert-danger"> | |
| <strong>BUG!</strong> Ctrl-C doesn't always work, and neither does the Abort button. When this happens, you may have to reload the page. | |
| </div> | |
| <h3>Reasoning with a node-resident program</h3> | |
| <p> | |
| Crucially, a node may host a Web Prolog <i>program</i> – a database, an expert system, a game engine, a digital assistant, a home control system, or what have you. If it does, a pengine running on the node has access to the predicates defined by this program. The program is typically maintained by the owner of the node. From the point of view of a client, the predicates exported by the program are as accessible as if they were built-in. As the owners of this node and for the sake of the tutorial we have installed a small number of such <em>node-resident programs</em>. Here's the first one, an instance of the well-known <i>family tree</i> example:</p> | |
| <pre id="a"> | |
| ancestor_descendant(X, Y) :- parent_child(X, Y). | |
| ancestor_descendant(X, Z) :- parent_child(X, Y), ancestor_descendant(Y, Z). | |
| parent_child(X, Y) :- mother_child(X, Y). | |
| parent_child(X, Y) :- father_child(X, Y). | |
| mother_child(trude, sally). | |
| father_child(tom, sally). | |
| father_child(tom, erica). | |
| father_child(mike, tom). | |
| </pre> | |
| <p> | |
| Who are descendants of Mike? To find out, here's how to call <code>ancestor_descendant/2</code>: | |
| </p> | |
| <pre id="ancestor1"> | |
| ?- ancestor_descendant(mike, Who). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#ancestor1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Unless clients are authorised they are not able to make any changes to node-resident code; only the owner is allowed to do so. However, by injecting source code in the workspace of their pengines, clients are able to <i>complement</i> the hosted program with code that they themselves have authored. For example, <code>assert/1</code> can be used for this purpose: | |
| </p> | |
| <pre id="ancestor2"> | |
| ?- assert((siblings(X, Y) :- | |
| parent_child(Z, X), | |
| parent_child(Z, Y), | |
| X @< Y)). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#ancestor2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Here's how to call <code>siblings/2</code>: | |
| </p> | |
| <pre id="ancestor3"> | |
| ?- siblings(X, Y). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#ancestor3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Sometimes <code>consult_text/1</code> is a better alternative. This predicate has a semantics very similar to <code>consult/1</code> in traditional Prolog, which, since it loads clauses from a file, is not available in Web Prolog. | |
| </p> | |
| <pre id="ancestor4"> | |
| ?- consult_text("siblings(X, Y) :- | |
| parent_child(Z, X), | |
| parent_child(Z, Y), | |
| X @< Y."). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#ancestor4")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Here's how to call <code>siblings/2</code>: | |
| </p> | |
| <pre id="ancestor5"> | |
| ?- siblings(X, Y). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#ancestor5")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="alert alert-info"> | |
| <strong>Concepts such as files, streams and sockets</strong> are too low-level for the Web, and thus we see, in our vision of a viable web logic programming language based on Prolog, no reason to support predicates for file I/O, socket programming or OS access. It is assumed that the host platform of a node will enable its owner to define predicates for such purposes in terms of constructs in the host programming language (which in our case is SWI-Prolog). | |
| </div> | |
| <div class="alert alert-success"> | |
| <h4>How does the sandboxing work?</h4> | |
| <p> | |
| In contrast to traditional Prolog and Erlang but similar to JavaScript, Web Prolog is a sandboxed language, open to the execution of untested or untrusted source code, possibly from unverified or untrusted clients without risking harm to the host machine or operating system. There are more than one way to ensure safety. In our proof-of-concept implementation each goal is examined before execution. This process unfolds the call-tree and verifies all called predicates against a whitelist. It fails on three occasions: | |
| </p> | |
| <ul> | |
| <li>If insufficient information is available to decide what | |
| will eventually be called, e.g. for goals such as <code>?-read(X),call(X).</code> | |
| Such errors are raised as <i>instantiation errors</i>. | |
| <li>If a predicate is found that is not whitelisted. Such | |
| errors are raised as <i>permission errors</i> | |
| <li>If a called predicate is not defined. Such errors are | |
| raised as <i>existence errors</i> | |
| </ul> | |
| <p> | |
| If any of the above happens, the error is printed, along with | |
| a chain of intermediate goals that should help finding the culprit. | |
| Note that the whitelist is incomplete, i.e., there are many | |
| SWI-Prolog predicates that can be considered safe that are not on the list. | |
| </p> | |
| </div> | |
| <div class="alert alert-info"> | |
| <strong>Tip!</strong> You want to be able to make changes to the node-resident code as well? If so, download the implementation of the present node from its <a target="_blank" href="https://github.com/Web-Prolog/swi-web-prolog">GitHub repo</a>, install it locally and run you own private node. Instructions can be found in the README file. | |
| </div> | |
| <h3>An expert system meta-interpreter</h3> | |
| <p> | |
| There was a time when expert systems were a big thing. There's even a Prolog text book that focuses on this. But expert systems never really went anywhere, so expert systems in Prolog, using the quite elegant meta-interpreter approach, never really went anywhere either. Nevertheless, the approach is worth presenting as an example. Here's a meta-interpreter implementing a tiny expert-system featuring a query-the-user facility which in interaction with the system allows you to determine if Tweety is a good pet or not. Before using the system, you have to consult it by clicking the Consult button located just below the code listing: | |
| </p> | |
| <pre id="expert1"> | |
| prove(true) :- !. | |
| prove((B, Bs)) :- !, | |
| prove(B), | |
| prove(Bs). | |
| prove(H) :- | |
| clause(H, B), | |
| prove(B). | |
| prove(H) :- | |
| askable(H), | |
| format(atom(Q), '~q?', [H]), | |
| pengine_input(Q, Answer), | |
| Answer == yes. | |
| good_pet(X) :- bird(X), small(X). | |
| good_pet(X) :- cuddly(X), yellow(X). | |
| bird(X) :- has_feathers(X), tweets(X). | |
| yellow(tweety). | |
| askable(tweets(_)). | |
| askable(small(_)). | |
| askable(cuddly(_)). | |
| askable(has_feathers(_)). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#expert1")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| <button onclick='load_example("/apps/swish/examples/expert-system.pl")' class="btn btn-md btn-link" type="button">Edit this example</button> | |
| </p> | |
| <p>Here's how to use the system: you'll need to answer "yes" or "no" to questions asked and the system's verdict will be either <code>true</code> (meaning that yes, Tweety is a good pet) or <code>false</code> (meaning no, Tweety isn't a good pet).</p> | |
| <pre id="expert2"> | |
| ?- prove(good_pet(tweety)). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#expert2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="alert alert-info"> | |
| <strong>Tip!</strong> Did you notice the link inviting you to edit the example? Selecting this link will take you to a proper editor that allows you to modify the program, reload it, and run it again. | |
| </div> | |
| <h3>Parsing and generating with a DCG</h3> | |
| <p> | |
| In Web Prolog as well as in traditional Prolog, Definite Clause Grammar (DCG) can be used to write grammars for natural or formal languages which can be used for parsing and/or generation. Here's a simple DCG for a tiny fragment of English: | |
| </p> | |
| <pre id="dcg1"> | |
| s(s(NP,VP)) --> np(NP, Num), vp(VP, Num). | |
| np(NP, Num) --> pn(NP, Num). | |
| np(np(Det,N), Num) --> det(Det, Num), n(N, Num). | |
| np(np(Det,N,PP), Num) --> det(Det, Num), n(N, Num), pp(PP). | |
| vp(vp(V,NP), Num) --> v(V, Num), np(NP, _). | |
| vp(vp(V,NP,PP), Num) --> v(V, Num), np(NP, _), pp(PP). | |
| pp(pp(P,NP)) --> p(P), np(NP, _). | |
| det(det(a), sg) --> [a]. | |
| det(det(the), _) --> [the]. | |
| pn(pn(john), sg) --> [john]. | |
| n(n(man), sg) --> [man]. | |
| n(n(men), pl) --> [men]. | |
| n(n(telescope), sg) --> [telescope]. | |
| v(v(sees), sg) --> [sees]. | |
| v(v(see), pl) --> [see]. | |
| v(v(saw), _) --> [saw]. | |
| p(p(with)) --> [with]. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#dcg1")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| <button onclick='load_example("/apps/swish/examples/grammar.pl")' class="btn btn-md btn-link" type="button">Edit this example</button> | |
| </p> | |
| <p> | |
| Below we show how to use the grammar for parsing. A solution is a Prolog term representing a parse tree. Note that since the sentence that we parse is ambiguous, we get two solutions: | |
| </p> | |
| <pre id="dcg2"> | |
| ?- phrase(s(Tree), [john,saw,a,man,with,a,telescope]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#dcg2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="alert alert-info"> | |
| <strong>Tip!</strong> Wouldn't it be nice if each solution of a goal such as this could be rendered as a graphical parse tree to the shell? Since we are dealing with a shell written in HTML, CSS and JavaScript, this can surely be a feature for the future. <strong>Tip in tip!</strong> SWISH can do it – see <a target="_blank" href="https://swish.swi-prolog.org/example/grammar.pl">here</a> and click the Run button. We'll say more about SWISH further down in the text. | |
| </div> | |
| <p> | |
| A DCG can be used for generation too. Here's a way to generate all sentences no longer than 8 words that are covered by the grammar. (The grammar covers infinitely many sentences, and that's why we need to restrict the length of those we want to generate): | |
| </p> | |
| <pre id="dcg3"> | |
| ?- forall((between(1,8,N), length(S,N), phrase(s(_),S)), io:writeln(S)). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#dcg3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="alert alert-info"> | |
| <strong>Tip!</strong> At this point, you may want to clear the shell. There are two ways to do this: either click the Clear button located in the panel below the shell or use your keyboard to produce a Ctrl-L. | |
| </div> | |
| <h3>Solving a sudoku puzzle with clp(fd)</h3> | |
| <p> | |
| <a target=“_blank” href=“https://en.m.wikipedia.org/wiki/Sudoku”>Sudoku</a> is one of the most popular puzzle games of all times, and as a puzzle over integers it can easily be solved with Constraint Logic Programming (CLP). The CLP library as well as the sudoku example is by <a href="https://www.metalevel.at/">Markus Triska</a>. | |
| </p> | |
| <pre id="clp1"> | |
| :- use_module(library(clpfd)). | |
| sudoku(Rows) :- | |
| length(Rows, 9), maplist(same_length(Rows), Rows), | |
| append(Rows, Vs), Vs ins 1..9, | |
| maplist(all_distinct, Rows), | |
| transpose(Rows, Columns), | |
| maplist(all_distinct, Columns), | |
| Rows = [A,B,C,D,E,F,G,H,I], | |
| blocks(A, B, C), blocks(D, E, F), blocks(G, H, I). | |
| blocks([], [], []). | |
| blocks([A,B,C|Bs1], [D,E,F|Bs2], [G,H,I|Bs3]) :- | |
| all_distinct([A,B,C,D,E,F,G,H,I]), | |
| blocks(Bs1, Bs2, Bs3). | |
| problem(1, [[_,_,_, _,_,_, _,_,_], | |
| [_,_,_, _,_,3, _,8,5], | |
| [_,_,1, _,2,_, _,_,_], | |
| [_,_,_, 5,_,7, _,_,_], | |
| [_,_,4, _,_,_, 1,_,_], | |
| [_,9,_, _,_,_, _,_,_], | |
| [5,_,_, _,_,_, _,7,3], | |
| [_,_,2, _,1,_, _,_,_], | |
| [_,_,_, _,4,_, _,_,9]]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#clp1")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| <button onclick='load_example("/apps/swish/examples/clpfd-sudoku.pl")' class="btn btn-md btn-link" type="button">Edit this example</button> | |
| </p> | |
| <p> | |
| Here's how to solve the example problem: | |
| </p> | |
| <pre id="clp2"> | |
| ?- problem(1, _Rows), sudoku(_Rows), maplist(io:writeln, _Rows). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#clp2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| This example also shows that there is more to logic programming than the simple backtracking search of traditional Prolog. CLP is just one example, and there are others such as probabilistic logic programming, abductive logic programming, disjunctive logic programming, and many other forms of logic programming. Indeed, Web Prolog seems to be rather well suited for interfacing to other logic-based languages of the knowledge representation world. | |
| </p> | |
| <h2>Web Prolog is a dialect of Erlang</h2> | |
| <p> | |
| We admit that seeing Web Prolog as a dialect of Erlang may be stretching the notion of a "dialect" a bit too far, but we really do believe it's possible to think of it as a variant of the Erlang language where we have "plugged out" its sequential part and "plugged in" sequential Prolog instead. | |
| </p> | |
| <p> | |
| Seen as an Erlang dialect, Web Prolog has many features not found in standard Erlang, features that it inherits from traditional Prolog: built-in backtracking search, unification, logic-based knowledge representation and reasoning, a dynamic database, meta-programming, user defined operators, a term expansion mechanism and definite clause grammars. Furthermore, Web Prolog tries to be more open than Erlang – open in the sense that the Web is open. Indeed, if it wasn't for the fact that Web Prolog resembles Prolog a lot more than it resembles Erlang, we might have called it "Web Erlang". | |
| </p> | |
| <p> | |
| Alternatively, and this is arguably easier, we can describe Web Prolog as a dialect of Prolog <i>extended</i> with constructs such as spawn, send and receive. We keep everything that core Prolog has to offer, and extend it with a number of those primitives that make Erlang such a great language for programming message-based concurrency. | |
| </p> | |
| <p> | |
| The choice between extending Prolog with Erlang-ish constructs and extending Erlang with Prolog-ish constructs was easy to make, and a lot has to do with syntax. Provided we can accept using a syntax which is relational rather than functional (and thus doesn't allow nesting calls), the surface syntax of Prolog can easily be adapted to express the needed Erlang-ish primitives. It would be a lot harder to express Prolog rules and other constructs using the syntax of Erlang, and we suspect that the final result wouldn't look very Erlang-ish at all. | |
| </p> | |
| <div class="alert alert-success"> | |
| <h4>Comparing Prolog and Erlang</h4> | |
| <h5>Where Prolog and Erlang are similar:</h5> | |
| <ul> | |
| <li>Erlang and Prolog are both dynamically typed languages.</li> | |
| <li>Variables are assign-once variables in both languages.</li> | |
| <li>Atoms and numbers look the same and mean the same in both languages.</li> | |
| <li>Lists look and behave the same in Prolog and Erlang.</li> | |
| <li>Erlang tuples are close to Prolog terms.</li> | |
| <li>In both languages, pattern matching is the dominant way to select clauses and pass values.</li> | |
| <li>Loops are normally implemented by means of recursion.</li> | |
| <li>Both languages comes with a garbage-collected runtime system and implement last call optimisation. | |
| <li>Both languages have hit a sweet spot on the scale between purity and practical usefulness.</li> | |
| </ul> | |
| <h5>Where Prolog and Erlang are different:</h5> | |
| <ul> | |
| <li>Prolog is relational, whereas Erlang is functional. This isn't a dramatic difference, however, since formally a function is just a special case of a relation. </li> | |
| <li>The assign-once variables in Prolog are also logic variables, but in Erlang they are not.</li> | |
| <li>In Erlang pattern matching is one-way, whereas Prolog uses full unification (except in receive clauses).</li> | |
| <li> Prolog features built-in backtracking search, unification, logic-based knowledge representation and reasoning, a dynamic database, meta-programming, user defined operators, a term expansion mechanism and definite clause grammars, Erlang does not.</li> | |
| <li>Prolog is homoiconic language, Erlang is not.</li> | |
| <li>Prolog is a logic programming language, Erlang is not.</li> | |
| <li>Erlang is an actor programming language, Prolog is not.</li> | |
| <li>Prolog and Erlang have different foci. A major motivation for Erlang was the need for <i>fault tolerance</i>. Concurrency and links can be seen as means to that end. For Web Prolog, fault tolerance is not the main focus.</li> | |
| </ul> | |
| </div> | |
| <p> | |
| The Web Prolog primitives for spawning and messaging are borrowed straight from Erlang. Thus, Web Prolog is an actor programming language suitable for concurrent and distributive programming, just like Erlang, while at the same time also retaining it's logic programming heritage. | |
| </p> | |
| <p> | |
| An important similarity between Erlang and Web Prolog is that spawning and sending | |
| work also in a distributed setting. Processes (or "actors" as we prefer to call them) may be created on remote nodes, and communication with them is transparent in the sense that communication with remote actors works exactly as communication with local actors. | |
| </p> | |
| <p> | |
| But here's a significant difference: the Erlang distribution mechanism is implemented using raw TCP/IP sockets whereas Web Prolog uses the WebSocket protocol. Not only does the use of websockets save us from having to implement our own message based protocol on top of TCP/IP, it also allows communication to pass through firewalls, and implements various security-related features such as methods for authentication, secure websockets over HTTPS and <a target="_blank" href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing">CORS (Cross-Origin Request Sharing)</a>. Such features are important in a distributed programming language that tries to be as open as the Web itself. | |
| </p> | |
| <h3>Spawning, sending and receiving</h3> | |
| <p> | |
| There are three operations required for concurrent programming in Web Prolog: spawning new processes, | |
| sending messages, and receiving messages. They borrow most of their semantics from Erlang and are implemented by the predicates <code>spawn/1-3</code>, <code>!/2</code> | |
| and <code>receive/1-2</code>, respectively. | |
| </p> | |
| <p> | |
| In the following example, <code>spawn/1</code> is used to create a new actor process running a goal | |
| that will solve the query <code>?-length([a,b,c],N)</code> and send the value of <code>N</code> | |
| back to the calling process identified as <code>Self</code> where it will be received by | |
| <code>receive/1</code>: | |
| </p> | |
| <pre id="spawning1"> | |
| ?- self(Self), | |
| spawn((length([a,b,c],N), Self ! N)), | |
| receive({M -> io:write(M)}). | |
| </pre> | |
| <pre id="spawning2" class="collapse"> | |
| 1> Self = self(), | |
| spawn(fun() -> Self ! length([a,b,c]) end), | |
| receive M -> io:write(M) end. | |
| % A little bit of unnesting makes the similarities stand out more: | |
| 2> Self = self(), | |
| spawn(fun() -> N = length([a,b,c]), Self ! N end), | |
| receive M -> io:write(M) end. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#spawning1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| <input type='checkbox' data-toggle="collapse" data-target="#spawning2"> Compare with Erlang | |
| </p> | |
| <p> | |
| Admittedly, this example was kind of silly since the spawned child process terminated almost immediately and <code>!/2</code> was only used to send the value of a variable to the top-level process. We'll look at more realistic examples further down in the text. | |
| </p> | |
| <div class="alert alert-info"> | |
| <strong>Tip!</strong> Did you notice the Compare with Erlang checkbox? Selecting it will show how you might write the comparable code in Erlang. | |
| </div> | |
| <h3>Messages deferred</h3> | |
| <p> | |
| If an invocation of <code>receive/1</code> doesn't match a message in the mailbox, the message is <i>deferred</i>, possibly to be handled later in the control flow of the process. The receive is still running, waiting for more messages to arrive, and for one that will match. This behaviour is particularly useful if we expect two messages, <code>hello</code> and <code>goodbye</code>, but are not sure which one will arrive first. If we insist on processing <code>hello</code> before <code>goodbye</code>, we can easily do that with receive: | |
| </p> | |
| <pre id="deferred1"> | |
| wait_hello :- | |
| receive({ | |
| hello -> | |
| io:write('Got hello!'), | |
| wait_goodbye | |
| }). | |
| wait_goodbye :- | |
| receive({ | |
| goodbye -> | |
| io:write('Got goodbye!') | |
| }). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#deferred1")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| </p> | |
| <p> | |
| To see what happens, let's put <code>goodbye</code> before <code>hello</code> in the top-level mailbox queue and then call <code>wait_hello/0</code>: | |
| </p> | |
| <pre id="deferred2"> | |
| ?- self(Self), | |
| Self ! goodbye, | |
| Self ! hello, | |
| wait_hello. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#deferred2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <h3>A priority queue example</h3> | |
| <p> | |
| Just like in Erlang, a receive clause in Web Prolog can have a <em>guard</em> placed just after the <code>when</code> operator and before the arrow <code>-></code>. In contrast to Erlang, which restricts guards to boolean combinations of simple tests, any kind of goal may serve as a guard in Web Prolog, although it's wise in general to keep guards as simple and efficient as possible and to avoid side effects. | |
| </p> | |
| <div class="alert alert-info"> | |
| <strong>Speaking of restrictions</strong> it is worth pointing out that as far as we can see, no restrictions need to be imposed on what may constitute a body in a receive clause. A body may be a goal that's non-deterministic or deterministic, and it may succeed or ultimately fail. Anything goes and you get what you ask for. | |
| </div> | |
| <p> | |
| As a way to demonstrate the use of a guard, as well as the use of two | |
| <code>receive/2</code> options that causes a goal to be run on timeout, here's a priority queue | |
| example borrowed from <a target="_blank" href="http://learnyousomeerlang.com">http://learnyousomeerlang.com</a>. | |
| Calling <code>important/1</code> will build a list of all messages with those with a priority | |
| above 10 coming first: | |
| </p> | |
| <pre id="priority1"> | |
| important(Messages) :- | |
| receive({ | |
| Priority-Message when Priority > 10 -> | |
| important(MoreMessages), | |
| Messages = [Message|MoreMessages]; | |
| after(0) -> | |
| normal(Messages) | |
| }). | |
| normal(Messages) :- | |
| receive({ | |
| _-Message -> | |
| normal(MoreMessages), | |
| Messages = [Message|MoreMessages]; | |
| after(0) -> | |
| Messages = [] | |
| }). | |
| </pre> | |
| <pre id="priority4" class="collapse"> | |
| -module(multiproc). | |
| -compile([export_all]). | |
| important() -> | |
| receive | |
| {Priority, Message} when Priority > 10 -> | |
| [Message | important()] | |
| after 0 -> | |
| normal() | |
| end. | |
| normal() -> | |
| receive | |
| {_, Message} -> | |
| [Message | normal()] | |
| after 0 -> | |
| [] | |
| end. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#priority1")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| <button onclick='load_example("/apps/swish/examples/priority_queue.pl")' class="btn btn-md btn-link" type="button">Edit this example</button> | |
| <input type='checkbox' data-toggle="collapse" data-target="#priority4"> Compare with Erlang | |
| </p> | |
| <p> | |
| To test our program, we use a combination of <code>self/1</code> and the send operator <code>!/2</code> to send four messages with integers serving as priority indicators to the mailbox of the top-level pengine. | |
| </p> | |
| <pre id="priority2"> | |
| ?- self(S), S ! 15-high, S ! 7-low, S ! 1-low, S ! 17-high. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#priority2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Finally, to compute a list where all the important messages appear before the less important ones we call <code>important/1</code>: | |
| </p> | |
| <pre id="priority3"> | |
| ?- important(Messages). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#priority3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="alert alert-danger"> | |
| <strong>BUG!</strong> As evident by the above example, the timeout mechanism doesn't take the form of two options at the moment, but uses what looks like a special kind of receive clause. This may look nice, but since it requires a semicolon where Erlang does not, we fear it might be confusing. | |
| </div> | |
| <h3>Some actors are servers</h3> | |
| <p> | |
| A typical use of <code>spawn/1-3</code> is to call a locally or remotely defined Web Prolog procedure that specifies the (usually longtime) behaviour of the actor process thus created – the kind of messages it will listen for (using <code>receive/1-2</code>), and the kind of messages that will be returned to the caller (by means of <code>!/2</code>). In Erlang textbooks, such actors are usually referred to as <em>servers</em>. It's common to distinguish between two kinds of servers, those that are <i>stateless</i>, and those that are <i>stateful</i>. | |
| </p> | |
| <p> | |
| Let's start by writing a stateless server. Using the primitives for spawning, sending and receiving, we can easily write a program that will respond with a <code>pong</code> message to any client that sends it a <code>ping</code> message. The pid of the client must be included in the <code>ping</code> message so that the server knows where to send the <code>pong</code>. First, let's consult the following source code: | |
| </p> | |
| <pre id="server1"> | |
| pong_server :- | |
| receive({ | |
| ping(From) -> | |
| From ! pong, | |
| pong_server | |
| }). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#server1")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| </p> | |
| <p> | |
| Here's how to spawn the server process: | |
| </p> | |
| <pre id="server2"> | |
| ?- spawn(pong_server, Pid, [ | |
| src_predicates([pong_server/0]) | |
| ]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#server2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| The <code>src_predicates</code> option is one of several options that can be used to inject | |
| source code into the workspace of the actor process to be created. As shown above, it takes a list of predicate indicators. (Other options that can be used for the purpose of injecting code are <code>src_text</code>, <code>src_list</code> and <code>src_uri</code>. Their uses will be demonstrated further down in the text.) | |
| </p> | |
| <p> | |
| We also need to determine the identity of our top-level pengine: | |
| </p> | |
| <pre id="server3"> | |
| ?- self(Self). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#server3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| To send messages to the mailbox of the spawned process the operator <code>!/2</code> is used. Note also the shell utility feature borrowed from SWI-Prolog, allowing us to reuse bindings resulting from the successful execution of a top-level goal in future top-level goals as <code>$Var</code>: | |
| </p> | |
| <pre id="server4"> | |
| ?- $Pid ! ping($Self). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#server4")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Web Prolog has a feature, borrowed from Erlang, in the form of a predicate <code>flush/0</code> | |
| that will print (and remove) any messages in the mailbox belonging to the top-level pengine. Let's | |
| call it: | |
| </p> | |
| <pre id="server5"> | |
| ?- flush. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#server5")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| So far, the similarity with Erlang has been rather obvious. But Web Prolog also allows us to define such a server in another way, using a repeat-fail loop rather than a recursive loop. This isn't possible in Erlang. | |
| </p> | |
| <pre id="server6"> | |
| repeat_fail_pong_server :- | |
| repeat, | |
| receive({ | |
| ping(From) -> | |
| From ! pong, | |
| fail | |
| }). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#server6")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| </p> | |
| <p> | |
| In Prolog, repeat-fail loops (or the more general concept of failure-driven loops) form the basis of the all-solutions predicates and predicates such as <code>forall/2</code>. However, most Prolog programmers like to keep such loops hidden as far under the hood as possible. We agree with this, so we like the server with the recursive loop better. (There are times, however, when a failure-driven loop is more or less unavoidable.) | |
| </p> | |
| <p> | |
| Let's start the new server and send ping messages to it. For a change, we use <code>receive/1</code> instead of <code>flush/0</code> to capture response messages. We send a ping twice, and wait for a pong response twice, and this time we'll do it all in a single call: | |
| </p> | |
| <pre id="server7"> | |
| ?- spawn(repeat_fail_pong_server, _Pid, [ | |
| src_predicates([repeat_fail_pong_server/0]) | |
| ]), | |
| self(_Self), | |
| _Pid ! ping(_Self), | |
| _Pid ! ping(_Self), | |
| receive({Msg1 -> true}), | |
| receive({Msg2 -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#server7")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="alert alert-success"> | |
| <h4>Surprised?</h4> | |
| <p> | |
| For an Erlang programmer the above use of <code>receive/1</code> may indeed come as a surprise. After all, the Prolog notions of <i>failure</i>, <i>backtracking</i> and the use of failure to force backtracking are foreign to Erlang. Prolog programmers may recognise a behaviour due to the fact that <code>receive/1-2</code> is a <i>semi-deterministic</i> predicate, i.e. a predicate that either fails or succeeds exactly once. You may want to compare <code>receive/1</code> with <code>read/1</code> in traditional Prolog, which is also semi-deterministic, and also serves the purpose of receiving data from the environment. But whereas <code>read/1</code> fails if the pattern (consisting of any term) in the argument doesn't unify with the term that is read, the only way that <code>receive/1-2</code> will fail is if the goal in the <i>body</i> of one of its receive clauses fails. After all, what else could it possibly mean if the goal in the body of a receive clause fails? We would argue that letting the whole call fail gives us the only reasonable semantics for <code>receive/1-2</code>. | |
| </p> | |
| </div> | |
| <h3>Node-resident actor processes</h3> | |
| <p> | |
| As we saw in an earlier example, the owner of a node may install node-resident programs, i.e. source code for predicates that can be accessed by any client to this node, as if they were built in. In addition, the owner may install <em>node-resident actor processes</em> – long-lived processes that can be accessed by any client. We show an example below, installed for the sake of this tutorial, which uses <code>register/2</code> to give the process a mnemonic name. | |
| </p> | |
| <pre> | |
| count_server(Count0) :- | |
| Count is Count0 + 1, | |
| receive({ | |
| count(From) -> | |
| From ! c(Count), | |
| count_server(Count) | |
| }). | |
| :- spawn(count_server(0), Pid), | |
| register(counter, Pid). | |
| </pre> | |
| <p> | |
| Note that this also happens to be our first example of a <i>stateful</i> server, as it uses the argument in <code>count_server/1</code> to carry around the state of the counter, i.e. the count itself. | |
| </p> | |
| <p> | |
| The registered name can be used instead of the pid when sending to the process: | |
| </p> | |
| <pre id="node-res-actor"> | |
| ?- self(_Self), | |
| counter ! count(_Self), | |
| receive({c(Count) -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#node-res-actor")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Did you receive a count greater than 1 when you clicked Ask? If so, it just means that someone else, running this tutorial somewhere else in the world, has clicked the Ask button before you did. Contrary to the state of a server that is spawned from your pengine, the state of a node-resident server is accessible from any client to the node that knows the pid or the registered name of the server. | |
| </p> | |
| <h3>A fridge simulation example</h3> | |
| <p> | |
| As a tastier example of how a process can be made to hold an updatable state during a conversation, and for the purpose of demonstrating a receive call that uses different receive clauses in order to listen for more than one kind of message, we have adapted a fridge simulation example from Fred Hebert's excellent book on Erlang, available at <a target="_blank" href="http://learnyousomeerlang.com">http://learnyousomeerlang.com</a>. | |
| </p> | |
| <p> | |
| Using <code>spawn/3</code>, the program creates a local process that simulates a fridge. The <code>src_text</code> option of <code>spawn/3</code> is used to pass the program to the actor process to be created. | |
| </p> | |
| <pre id="fridge1"> | |
| start(Pid) :- | |
| spawn(fridge([]), Pid, [ | |
| src_text(" | |
| fridge(FoodList0) :- | |
| receive({ | |
| store(From, Food) -> | |
| self(Self), | |
| From ! ok(Self), | |
| fridge([Food|FoodList0]); | |
| take(From, Food) -> | |
| self(Self), | |
| ( select(Food, FoodList0, FoodList) | |
| -> From ! ok(Self, Food), | |
| fridge(FoodList) | |
| ; From ! not_found(Self), | |
| fridge(FoodList0) | |
| ); | |
| terminate -> | |
| true | |
| }). | |
| ") | |
| ]). | |
| </pre> | |
| <pre id="fridge2" class="collapse"> | |
| -module(kitchen). | |
| -compile([export_all]). | |
| start -> | |
| spawn(kitchen, fridge, [[]]). | |
| fridge(FoodList) -> | |
| receive | |
| {From, {store, Food}} -> | |
| From ! {self(), ok}, | |
| fridge([Food|FoodList]); | |
| {From, {take, Food}} -> | |
| case lists:member(Food, FoodList) of | |
| true -> | |
| From ! {self(), {ok, Food}}, | |
| fridge(lists:delete(Food, FoodList)); | |
| false -> | |
| From ! {self(), not_found}, | |
| fridge(FoodList) | |
| end; | |
| terminate -> | |
| ok | |
| end. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#fridge1")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| <button onclick='load_example("/apps/swish/examples/fridge.pl")' class="btn btn-md btn-link" type="button">Edit this example</button> | |
| <input type='checkbox' data-toggle="collapse" data-target="#fridge2"> Compare with Erlang | |
| </p> | |
| <p> | |
| Let's take our fridge for a spin by running the following goal ... | |
| </p> | |
| <pre id="fridge3"> | |
| ?- start(Pid), | |
| self(Self), | |
| Pid ! store(Self, meat), | |
| Pid ! store(Self, cheese), | |
| Pid ! take(Self, meat), | |
| Pid ! take(Self, cheese), | |
| Pid ! take(Self, cheese). | |
| Pid ! terminate. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#fridge3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| ... and check the content of the top-level mailbox afterwards: | |
| </p> | |
| <pre id="fridge4"> | |
| ?- flush. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#fridge4")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="alert alert-info"> | |
| <strong>Tip! (for Prologers)</strong> Yep, Fred Hebert's book is awesome, and it's free. Read it online at <a target="_blank" href="http://learnyousomeerlang.com">http://learnyousomeerlang.com</a>. If you are a Prologer you could probably start from <a target="_blank" href="http://learnyousomeerlang.com/the-hitchhikers-guide-to-concurrency">here</a>. Another nice resource for learning more about Erlang is the video course at <a target="_blank" href="https://www.cs.kent.ac.uk/ErlangMasterClasses">https://www.cs.kent.ac.uk/ErlangMasterClasses</a>. | |
| </div> | |
| <div class="alert alert-info"> | |
| <strong>Tip! (for Erlangers)</strong> An interesting and newly written Prolog online book authored by Markus Triska is available at <a target="_blank" href="https://www.metalevel.at/prolog">https://www.metalevel.at/prolog</a>. If you prefer to watch a video clip, <a target="_blank" href="https://www.youtube.com/watch?v=G_eYTctGZw8">https://www.youtube.com/watch?v=G_eYTctGZw8</a> is a great talk by Michael Hendricks. | |
| </div> | |
| <!-- | |
| <h3>Using all-solutions predicates</h3> | |
| <p> | |
| You can do it, they are there: | |
| </p> | |
| <pre id="findall1"> | |
| ?- findall(_I, between(1, 10, _I), L). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#findall1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| One thing that a top-level pengine can do, but that the traditional shell doesn't really take advantage of, is to generate batches of n solutions at a time. (The SWISH GUI is much better at this!) | |
| </p> | |
| <p> | |
| Not all is lost, however, since we can call <code>findnsols/3</code>: | |
| </p> | |
| <pre id="findnsols1"> | |
| ?- findnsols(4, _I, between(1, 10, _I), L). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#findnsols1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| ... | |
| </p> | |
| --> | |
| <h3>Links, monitors and two kinds of exits</h3> | |
| <p> | |
| Links in Web Prolog are somewhat simpler than links in Erlang, and in contrast to Erlang's bi-directional links, they are uni-directional. As argued <a target="_blank" href="http://happy-testing.com/hans/papers/EW2010-UnifiedSemantics.pdf">here</a>, uni-directional links simplify things and don't harm expressivity. The only kind of link currently supported in Web Prolog comes in the form of an option <code>link</code> to <code>spawn/3</code>. Its value is <code>true</code> by default, which causes a child process to terminate if its parent does. Only authorised clients can set it to <code>false</code> and thus an unauthorised client cannot spawn a process which is <i>not</i> linked to the process that spawned it. This is to avoid leaving orphaned processes around on a node. For the same reason, <code>unlink/1</code> isn't available to unauthorised clients. | |
| </p> | |
| <p> | |
| An obvious and closeby example of the use of a link is that when you leave (or reload) this tutorial, your top-level pengine as well as any actors that you may have spawned from the top-level are forced to terminate. | |
| </p> | |
| <p> | |
| Monitors in Web Prolog are also somewhat simpler than monitors in Erlang. The only kind of monitor currently supported comes in the form of an option to <code>spawn/3</code>. The notion of a <i>monitor reference</i> isn't (yet) supported, and <i>demonitoring</i> is therefore not possible. | |
| </p> | |
| <div class="alert alert-danger"> | |
| <strong>Heads up!</strong> How linking and monitoring is specified may change in the future since we are not yet sure we have the right design. Here we would appreciate advice from the Erlang community – what would you do? Comments are welcome! | |
| </div> | |
| <p> | |
| Just like Erlang, Web Prolog offers two ways to force the termination of an actor: | |
| </p> | |
| <ul> | |
| <li> | |
| <i>Internal exits</i> are triggered by calling the predicate <code>exit/1</code> which makes the | |
| current process stop its execution. | |
| </li> | |
| <li> | |
| <i>External exits</i> are called with <code>exit/2</code> and can be used to terminate any process. But only if you know its pid or its registered name, and only if you own it. | |
| </li> | |
| </ul> | |
| <p>Here's an example of the use of an internal exit and how it interacts with the <code>monitor</code> option:</p> | |
| <pre id="exit1"> | |
| ?- spawn(exit(hello), _, [ | |
| monitor(true) | |
| ]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#exit1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Let's look in the top-level mailbox at what the monitoring produced: | |
| </p> | |
| <pre id="exit2"> | |
| ?- flush. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#exit2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p>Here's an example of the use of an external exit:</p> | |
| <pre id="exit3"> | |
| ?- spawn(sleep(1), Pid, [ | |
| monitor(true) | |
| ]), | |
| exit(Pid, goodbye). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#exit3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Again, let's look at the result of monitoring the process: | |
| </p> | |
| <pre id="exit4"> | |
| ?- flush. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#exit4")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <h3>A publish-subscribe service</h3> | |
| <p> | |
| For the sake of the tutorial, a node-resident actor implementing a simple publish-subscribe service has been installed on the node. Here's the source code: | |
| </p> | |
| <pre> | |
| pubsub_service(Subscribers0) :- | |
| receive({ | |
| publish(Message) -> | |
| forall(member(Pid, Subscribers0), Pid ! msg(Message)), | |
| pubsub_service(Subscribers0); | |
| subscribe(Pid) -> | |
| pubsub_service([Pid|Subscribers0]); | |
| unsubscribe(Pid) -> | |
| ( select(Pid, Subscribers0, Subscribers) | |
| -> pubsub_service(Subscribers) | |
| ; pubsub_service(Subscribers0) | |
| ). | |
| }). | |
| :- spawn(pubsub_service([]), Pid), | |
| register(pubsub_service, Pid). | |
| </pre> | |
| <p> | |
| In the following example we subscribe to the service, and (too lazy to write a recursive loop) invoke a repeat-fail loop waiting for messages to arrive from it: | |
| </p> | |
| <pre id="pubsub1"> | |
| ?- self(Self), | |
| pubsub_service ! subscribe(Self), | |
| repeat, | |
| io:write("Waiting for a message ..."), | |
| receive({ | |
| msg(Message) -> | |
| io:format("Received: ~p", [Message]), | |
| fail | |
| }). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pubsub1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| The above call is blocking, so to see the effect of publishing a term to the service, you will either have to wait for someone else to publish to it, or (more realistically) open this tutorial in another page or tab and do: | |
| </p> | |
| <pre id="pubsub2"> | |
| ?- pubsub_service ! publish(hello). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pubsub2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="alert alert-danger"> | |
| <strong>BUG!</strong> Unfortunately, a bug stops you from aborting the blocking repeat-fail loop using Ctrl-C. Your only way out at this point is to reload the page. Sorry about that. | |
| </div> | |
| <h3>Actors playing ping-pong</h3> | |
| <p> | |
| Erlang-style asynchronous communication is key to the kind of concurrent programming that | |
| Web Prolog's actor abstraction supports. In the following example, adapted from the example at <a target="_blank" href="http://erlang.org/doc/getting_started/conc_prog.html#id69544">http://erlang.org/doc/getting_started/conc_prog.html</a>, two processes are first | |
| created and then start sending messages to each other a specified number of times. | |
| </p> | |
| <pre id="pingpong1"> | |
| ping(0, Pong_Pid) :- | |
| Pong_Pid ! finished, | |
| io:format('Ping finished'). | |
| ping(N, Pong_Pid) :- | |
| self(Self), | |
| Pong_Pid ! ping(Self), | |
| receive({ | |
| pong -> | |
| io:format('Ping received pong') | |
| }), | |
| N1 is N - 1, | |
| ping(N1, Pong_Pid). | |
| pong :- | |
| receive({ | |
| ping(Ping_Pid) -> | |
| io:format('Pong received ping'), | |
| Ping_Pid ! pong, | |
| pong; | |
| finished -> | |
| io:format('Pong finished') | |
| }). | |
| start :- | |
| spawn(pong, Pong_Pid, [ | |
| src_predicates([pong/0]) | |
| ]), | |
| spawn(ping(3, Pong_Pid), _, [ | |
| src_predicates([ping/2]) | |
| ]). | |
| </pre> | |
| <pre id="pingpong3" class="collapse"> | |
| -module(tut15). | |
| -export([start/0, ping/2, pong/0]). | |
| ping(0, Pong_PID) -> | |
| Pong_PID ! finished, | |
| io:format("ping finished"); | |
| ping(N, Pong_PID) -> | |
| Pong_PID ! {ping, self()}, | |
| receive | |
| pong -> | |
| io:format("Ping received pong") | |
| end, | |
| ping(N - 1, Pong_PID). | |
| pong() -> | |
| receive | |
| finished -> | |
| io:format("Pong finished"); | |
| {ping, Ping_PID} -> | |
| io:format("Pong received ping"), | |
| Ping_PID ! pong, | |
| pong() | |
| end. | |
| start() -> | |
| Pong_PID = spawn(tut15, pong, []), | |
| spawn(tut15, ping, [3, Pong_PID]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#pingpong1")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| <button onclick='load_example("/apps/swish/examples/pingpong.pl")' class="btn btn-md btn-link" type="button">Edit this example</button> | |
| <input type='checkbox' data-toggle="collapse" data-target="#pingpong3"> Compare with Erlang | |
| </p> | |
| <p>We call <code>start/0</code> to start the processes:</p> | |
| <pre id="pingpong2"> | |
| ?- start. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pingpong2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| In our next example – our first example of distributed programming with actors – we move the "ponger" to another node: | |
| </p> | |
| <pre id="pingpong4"> | |
| ping(0, Pong_Pid) :- | |
| Pong_Pid ! finished, | |
| io:format('Ping finished',[]). | |
| ping(N, Pong_Pid) :- | |
| self(Self), | |
| Pong_Pid ! ping(Self), | |
| receive({ | |
| pong -> | |
| io:format('Ping received pong',[]) | |
| }), | |
| N1 is N - 1, | |
| ping(N1, Pong_Pid). | |
| pong :- | |
| receive({ | |
| ping(Ping_Pid) -> | |
| io:format('Pong received ping',[]), | |
| Ping_Pid ! pong, | |
| pong; | |
| finished -> | |
| io:format('Pong finished',[]) | |
| }). | |
| start :- | |
| spawn(pong, Pong_Pid, [ | |
| node('{{host}}'), % This line is our only addition! | |
| src_predicates([pong/0]) | |
| ]), | |
| spawn(ping(3, Pong_Pid), _, [ | |
| src_predicates([ping/2]) | |
| ]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#pingpong4")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| </p> | |
| <p>Again, we call <code>start/0</code> to start the process.</p> | |
| <pre id="pingpong5"> | |
| ?- start. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pingpong5")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| So, why do we get the output of the "pinger" only, and not the output written by the "ponger"? | |
| The reason is that a shell can only receive and render output that is written from | |
| actors that 1) live on the same node as the top-level pengine to which the shell is attached, and 2) are descendants of this top-level pengine. If we figure out a way to do it, this restriction may be lifted in the future. | |
| </p> | |
| <!-- | |
| <p> | |
| You should also note that <code>pengine_input/2</code> | |
| <code>pengine_output/1</code> and <code>send/2</code> are the principal I/O predicates in Web Prolog | |
| (along with <code>io:read/1</code>). <code>io:write/1</code> should be considered a debugging tool). | |
| </p> | |
| --> | |
| <div class="alert alert-warning"> | |
| <strong>Heads up!</strong> Note that we lie a little when we write that we moved the "ponger" process to another node. We are actually using the same node as the pinger is running on. The reason is that we don't want to run more than one node for the purpose of this tutorial. | |
| </div> | |
| <h3>Porting Erlang programs to Web Prolog</h3> | |
| <p> | |
| Below, we have ported | |
| <a target="_blank" href="https://github.com/acmeism/RosettaCodeData/blob/master/Task/Dining-philosophers/Erlang/dining-philosophers.erl"> | |
| an Erlang program</a> that uses seven concurrently running actor processes to solve <a target="_blank" href="https://en.wikipedia.org/wiki/Dining_philosophers_problem">the Dining Philosophers problem</a> to Web Prolog and made it node-resident. | |
| </p> | |
| <pre> | |
| sleep :- | |
| Time is random_float/10, | |
| sleep(Time). | |
| doForks(ForkList) :- | |
| receive({ | |
| {grabforks, {Left, Right}} -> | |
| subtract(ForkList, [Left,Right], ForkList1), | |
| doForks(ForkList1); | |
| {releaseforks, {Left, Right}} -> | |
| doForks([Left, Right| ForkList]); | |
| {available, {Left, Right}, Sender} -> | |
| ( member(Left, ForkList), | |
| member(Right, ForkList) | |
| -> Bool = true | |
| ; Bool = false | |
| ), | |
| Sender ! {areAvailable, Bool}, | |
| doForks(ForkList); | |
| {die} -> | |
| io:format("Forks put away.") | |
| }). | |
| areAvailable(Forks, Have) :- | |
| self(Self), | |
| forks ! {available, Forks, Self}, | |
| receive({ | |
| {areAvailable, false} -> | |
| Have = false; | |
| {areAvailable, true} -> | |
| Have = true | |
| }). | |
| processWaitList([], false). | |
| processWaitList([H|T], Result) :- | |
| {Client, Forks} = H, | |
| areAvailable(Forks, Have), | |
| ( Have == true | |
| -> Client ! {served}, | |
| Result = true | |
| ; Have == false | |
| -> processWaitList(T, Result) | |
| ). | |
| doWaiter([], 0, 0, false) :- | |
| forks ! {die}, | |
| io:format("Waiter is leaving."), | |
| diningRoom ! {allgone}. | |
| doWaiter(WaitList, ClientCount, EatingCount, Busy) :- | |
| receive({ | |
| {waiting, Client} -> | |
| WaitList1 = [Client|WaitList], % add to waiting list | |
| ( Busy == false, | |
| EatingCount<2 | |
| -> processWaitList(WaitList1, Busy1) | |
| ; Busy1 = Busy | |
| ), | |
| doWaiter(WaitList1, ClientCount, EatingCount, Busy1); | |
| {eating, Client} -> | |
| subtract(WaitList, [Client], WaitList1), | |
| EatingCount1 is EatingCount+1, | |
| doWaiter(WaitList1, ClientCount, EatingCount1, false); | |
| {finished} -> | |
| processWaitList(WaitList, R1), | |
| EatingCount1 is EatingCount-1, | |
| doWaiter(WaitList, ClientCount, EatingCount1, R1) ; | |
| {leaving} -> | |
| ClientCount1 is ClientCount - 1, | |
| flag(left_received, N, N+1), | |
| doWaiter(WaitList, ClientCount1, EatingCount, Busy) | |
| }). | |
| philosopher(Name, _Forks, 0) :- | |
| io:format("~s is leaving.", [Name]), | |
| waiter ! {leaving}, | |
| flag(left, N, N+1). | |
| philosopher(Name, Forks, Cycle) :- | |
| self(Self), | |
| io:format("~s is thinking (cycle ~w).", [Name, Cycle]), | |
| sleep, | |
| io:format("~s is hungry (cycle ~w).", [Name, Cycle]), | |
| waiter ! {waiting, {Self, Forks}}, % sit at table | |
| receive({ | |
| {served} -> | |
| forks ! {grabforks, Forks}, % grab forks | |
| waiter ! {eating, {Self, Forks}}, % start eating | |
| io:format("~s is eating (cycle ~w).", [Name, Cycle]) | |
| }), | |
| sleep, | |
| forks ! {releaseforks, Forks}, % put forks down | |
| waiter ! {finished}, | |
| Cycle1 is Cycle - 1, | |
| philosopher(Name, Forks, Cycle1). | |
| dining :- | |
| AllForks = [1, 2, 3, 4, 5], | |
| Clients = 5, | |
| self(Self), | |
| register(diningRoom, Self), | |
| spawn(doForks(AllForks), ForksPid), | |
| register(forks, ForksPid), | |
| spawn(doWaiter([], Clients, 0, false), WaiterPid), | |
| register(waiter, WaiterPid), | |
| Life_span = 20, | |
| spawn(philosopher('Aristotle', {5, 1}, Life_span)), | |
| spawn(philosopher('Kant', {1, 2}, Life_span)), | |
| spawn(philosopher('Spinoza', {2, 3}, Life_span)), | |
| spawn(philosopher('Marx', {3, 4}, Life_span)), | |
| spawn(philosopher('Russel', {4, 5}, Life_span)), | |
| receive({ | |
| {allgone} -> | |
| io:format("Dining room closed.") | |
| }), | |
| unregister(diningRoom). | |
| </pre> | |
| <p>Let's run it:</p> | |
| <pre id="porting1"> | |
| ?- dining. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#porting1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Due to the many similarities between Erlang and Web Prolog, porting the code was fairly straightforward. We could even take advantage of the fact that an Erlang tuple such as <code>{grabforks,{Left,Right}}</code> is valid syntax in Prolog too, where it's not a tuple but a complex term of the form <code>{}((grabforks,{}((Left,Right))))</code>. In general, however, we recommend that (in a second pass of a porting effort) such terms are replaced by less complex ones. In this case, <code>grabforks(Left-Right)</code> would probably be a good choice. | |
| </p> | |
| <!-- | |
| <div class="alert alert-success"> | |
| <h4>More about translation</h4> | |
| <p> | |
| Whereas translation from Erlang to Web Prolog is fairly easy, translation in the other direction will in general be much harder. This should tell us something about the difference in expressivity between the two languages. | |
| </p> | |
| </div> | |
| --> | |
| <h2>Web Prolog is a hybrid of Prolog and Erlang</h2> | |
| <p> | |
| We have shown that most Prolog programs can be ported to Web Prolog (that's easy) and we have demonstrated that many Erlang programs can be ported as well (even though that's somewhat trickier). Is all well and dandy then? Does it really work? Can Web Prolog be considered a reasonably hybrid between Prolog and Erlang? | |
| </p> | |
| <p> | |
| It's probably wise to be suspicious of unexpected interactions between language features and possible impedance mismatches between the two paradigms – between Prolog's relational, non-deterministic programming model and Erlang's functional and message passing model. How well do the Erlang-ish constructs mix with Prolog, with the dynamic database and with backtracking for example? | |
| </p> | |
| <h3>A dynamic database fridge</h3> | |
| <p> | |
| As hinted at in the introduction, in Web Prolog every actor is equipped with its own private <i>dynamic database</i>. This gives us an alternative way to maintain the state of an actor. Here's how the fridge simulation may be written using assert and retract: | |
| </p> | |
| <pre id="dynamic1"> | |
| start(Pid) :- | |
| spawn(fridge, Pid, [ | |
| src_text(" | |
| :- dynamic food/1. | |
| fridge :- | |
| receive({ | |
| store(From, Food) -> | |
| assert(food(Food)), | |
| self(Self), | |
| From ! ok(Self), | |
| fridge; | |
| take(From, Food) -> | |
| self(Self), | |
| ( retract(food(Food)) | |
| -> From ! ok(Self, Food), | |
| fridge | |
| ; From ! not_found(Self), | |
| fridge | |
| ); | |
| terminate -> | |
| true | |
| }). | |
| ") | |
| ]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#dynamic1")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| </p> | |
| <p> | |
| Let's try this by running the following goal ... | |
| </p> | |
| <pre id="dynamic2"> | |
| ?- start(Pid), | |
| self(Self), | |
| Pid ! store(Self, meat), | |
| Pid ! store(Self, cheese), | |
| Pid ! take(Self, meat), | |
| Pid ! take(Self, cheese), | |
| Pid ! take(Self, cheese). | |
| Pid ! terminate. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#dynamic2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| ... and then check the content of the top-level mailbox: | |
| </p> | |
| <pre id="dynamic3"> | |
| ?- flush. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#dynamic3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| The dynamic database is a non-logical extension to Prolog and the use of it for managing state is usually frowned upon by Prolog programmers since changes to the database are not reverted on backtracking. Also, clauses of dynamic predicates are like destructible global variables and therefore subject to all the common problems with such objects. (See <a target="_blank" href="http://www.swi-prolog.org/howto/database.txt">here</a> for good advice when dealing with the dynamic database in SWI-Prolog.) However, for storing really, really large quantities of food in our fridge using the dynamic database may still be the best solution. | |
| </p> | |
| <p> | |
| A Prolog-style dynamic database is not something that Erlang has, and we need to determine if it could lead to problems when mixed with Web Prolog. The answer is "no" since Erlang does in fact have something which plays the same role as the dynamic database. In Erlang, each process has a local store called the <i>process dictionary</i> and a handful of functions for manipulating it are built into the language. Process dictionaries are private and cannot be modified by any other process. Erlang programmers are advised again using them since they destroy referential transparency and make debugging difficult. (See <a target="_blank" href="https://ferd.ca/on-the-use-of-the-process-dictionary-in-erlang.html">here</a> for good advice when dealing with the process dictionary in Erlang.) | |
| </p> | |
| <p> | |
| One cannot help noticing a similarity here. This leads us to believe that the dynamic database in Web Prolog is not more dangerous than the process dictionary in Erlang. It is arguably also more powerful and useful than the process dictionary, which probably means that programmers are tempted to use it more often than is recommended. | |
| </p> | |
| <p> | |
| The question if Web Prolog should allow for some kind of memory over and above what can be passed around in the arguments of predicates or asserted in the private dynamic database is interesting. Here, Erlang supports a built-in term storage known as <i>ETS</i> and the distributed database <i>Mnesia</i>. In line with the focus of Web Prolog on the web domain, a solution based on a transactional RDF database might be appropriate and is easily implemented in (at least) SWI-Prolog, which has <a target="_blank" href="http://www.swi-prolog.org/pldoc/doc_for?object=section(%27packages/semweb.html%27)">a library</a> for it. | |
| </p> | |
| <h3>Controlling backtracking</h3> | |
| <p> | |
| How well do the Erlang-ish messaging constructs combine with backtracking? Let's turn to a tiny but illustrative example which seems to show that the mix is both sound and easy to understand. | |
| </p> | |
| <p> | |
| Suppose the goal given in the first argument to <code>spawn/2</code> has more than one answer, a query such as <code>?-ancestor_descendant(mike,Who)</code> for example. Below, we call the query, send the result back to the calling process, and then use <code>receive/1</code> to listen for a message of the form <code>next</code> or <code>stop</code>: | |
| </p> | |
| <pre id="pre-pengine1"> | |
| ?- self(Self), | |
| spawn(( ancestor_descendant(mike, Who), | |
| Self ! Who, | |
| receive({ | |
| next -> fail; | |
| stop -> | |
| Self ! stopped | |
| }) | |
| ), Pid). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pre-pengine1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p>The first answer to the query should now be present in the mailbox of the top-level pengine and can be inspected if we call <code>flush/0</code>:</p> | |
| <pre id="pre-pengine2"> | |
| ?- flush. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pre-pengine2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| At this point, the receive call is blocking while waiting for messages to show up in the mailbox. To check if there are more solutions to our query we send a message <code>next</code> to the actor. This will cause the receive to fail and thus force the backtracking that will trigger the search for the next solution: | |
| </p> | |
| <pre id="pre-pengine3"> | |
| ?- $Pid ! next. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pre-pengine3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Did we get more solutions?: | |
| </p> | |
| <pre id="pre-pengine4"> | |
| ?- flush. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pre-pengine4")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Below we show how to stop asking for more solutions to the query. This time we wait for the expected <code>stop</code> message using receive instead of flushing the mailbox: | |
| </p> | |
| <pre id="pre-pengine5"> | |
| ?- $Pid ! stop, | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pre-pengine5")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| This suggests that Prolog's backtracking mechanism is perfectly compatible with and also | |
| complements the proposed Erlang-like mechanism for spawning actors and handling the communication | |
| between them. We need to observe, however, that the goal to be solved in the above example is | |
| hard-coded into the program, and that the program handles neither failure of the spawned goal, nor | |
| exceptions thrown by it. | |
| </p> | |
| <h3>A generic encapsulated search procedure</h3> | |
| <p> | |
| In this section we define a generic encapsulated search predicate by using a meta-predicate <code>call_cleanup/2</code> | |
| and by specifying a small set of custom messages carrying answers | |
| that needs to be returned to the calling process. Note that <code>call_cleanup/2</code> is | |
| here used not only to call a goal, but also to check if any choice points remain after the goal | |
| has been called or backtracked into. | |
| </p> | |
| <pre id="encapsulated-search1"> | |
| search(Query, Pid) :- | |
| self(Self), | |
| spawn(query(Query, Pid, Self), Pid, [ | |
| monitor(true), | |
| src_predicates([query/3]) | |
| ]). | |
| query(Query, Self, Parent) :- | |
| call_cleanup(Query, Det=true), | |
| ( var(Det) | |
| -> Parent ! success(Self, Query, true), | |
| receive({ | |
| next -> fail; | |
| stop -> true | |
| }) | |
| ; Parent ! success(Self, Query, false) | |
| ). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='consult("#encapsulated-search1")' class="btn btn-xs btn-info" type="button">Consult</button> | |
| </p> | |
| <p> | |
| Here's how to call <code>search/2</code> with a query: | |
| </p> | |
| <pre id="encapsulated-search2"> | |
| ?- search(member(X,[a,b,c]), Pid). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#encapsulated-search2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p>Let's flush to inspect the first solution:</p> | |
| <pre id="encapsulated-search3"> | |
| ?- flush. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#encapsulated-search3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p>We then ask for the next solution:</p> | |
| <pre id="encapsulated-search4"> | |
| ?- $Pid ! next, | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#encapsulated-search4")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p>Tell the actor to stop:</p> | |
| <pre id="encapsulated-search6"> | |
| ?- $Pid ! stop, | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#encapsulated-search6")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| What about failures and exceptions? Well, since we used a monitor the information provided in the <code>down</code> message tells us what we need to know. Try the following queries and see for yourself: | |
| </p> | |
| <pre id="encapsulated-search8"> | |
| ?- search(false, _Pid), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#encapsulated-search8")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <pre id="encapsulated-search9"> | |
| ?- search(not_defined, _Pid), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#encapsulated-search9")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| This section offered some insight into how encapsulated search works when understood as an actor that adheres to a particular protocol. Seemed tricky? Here's the good news: you don't need to build your own implementations of encapsulated search, but can rely on the built-in pengine abstraction – the special kind of actor introduced in the beginning of the tutorial. | |
| </p> | |
| <h2>The pengine behaviour</h2> | |
| <p> | |
| A pengine is like a first-class interactive Prolog top-level, accessible from Web Prolog as well as from other programming languages. For example, recall that the shell running in the area to the right is an application written in HTML, CSS and JavaScript that is talking to a pengine – <i>your</i> pengine. In this section we'll show how to spawn other pengines, and how to use your shell to talk to them via your top-level pengine. | |
| </p> | |
| <p> | |
| Whereas encapsulated search (as implemented above) allows only one query per process created, a pengine optionally allows a full <i>session</i> per process, possibly involving many queries. This is why we sometimes refer to a pengine as an <i>encapsulated Prolog session</i>. | |
| </p> | |
| <p> | |
| As we stated in the introduction, a pengine is also an actor, a special <i>kind</i> of actor. Its behaviour differs from the behaviours of other kinds of actors in that it adheres to a protocol specific to the communication between a client and a pengine – the Pengine Communication Protocol (PCP). The statechart below depicts the PCP. The client can be any kind of process (including another pengine) capable of sending the type of messages and signals in bold to the pengine. The pengine is responsible for returning the type of messages with a leading / back to the client. | |
| </p> | |
| <!-- | |
| <div class="alert alert-success"> | |
| <p> | |
| <strong>Suggestion:</strong> What we suggest here is that, contrary to what is probably the received view, it is the actor and <i>not</i> the Prolog top-level that should be regarded as the most natural locus of computation in Prolog. | |
| </p> | |
| </div> | |
| --> | |
| <svg style="margin-left:5%;margin-right:5%" version="1.1" viewBox="0.0 0.0 624.0 331.501312335958" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="p.0"><path d="m0 0l624.0 0l0 331.5013l-624.0 0l0 -331.5013z" clip-rule="nonzero"></path></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l624.0 0l0 331.5013l-624.0 0z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m42.624672 50.679825l0 0c0 -10.85582 8.800381 -19.656202 19.656204 -19.656202l438.8293 0c5.2131653 0 10.212769 2.0709133 13.899017 5.7571697c3.6862793 3.6862526 5.757202 8.685883 5.757202 13.899033l0 236.7191c0 10.855804 -8.8003845 19.656189 -19.65622 19.656189l-438.8293 0c-10.8558235 0 -19.656204 -8.8003845 -19.656204 -19.656189z" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m42.624672 50.679825l0 0c0 -10.85582 8.800381 -19.656202 19.656204 -19.656202l438.8293 0c5.2131653 0 10.212769 2.0709133 13.899017 5.7571697c3.6862793 3.6862526 5.757202 8.685883 5.757202 13.899033l0 236.7191c0 10.855804 -8.8003845 19.656189 -19.65622 19.656189l-438.8293 0c-10.8558235 0 -19.656204 -8.8003845 -19.656204 -19.656189z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m362.81366 225.3327l0 0c0 -2.6556091 2.152771 -4.8084106 4.80838 -4.8084106l95.26511 0c1.2752686 0 2.498291 0.50660706 3.400055 1.4083557c0.9017334 0.90174866 1.4083252 2.1247864 1.4083252 3.400055l0 19.233597c0 2.6555939 -2.152771 4.8083954 -4.80838 4.8083954l-95.26511 0c-2.6556091 0 -4.80838 -2.1528015 -4.80838 -4.8083954z" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m362.81366 225.3327l0 0c0 -2.6556091 2.152771 -4.8084106 4.80838 -4.8084106l95.26511 0c1.2752686 0 2.498291 0.50660706 3.400055 1.4083557c0.9017334 0.90174866 1.4083252 2.1247864 1.4083252 3.400055l0 19.233597c0 2.6555939 -2.152771 4.8083954 -4.80838 4.8083954l-95.26511 0c-2.6556091 0 -4.80838 -2.1528015 -4.80838 -4.8083954z" fill-rule="evenodd"></path><path fill="#000000" d="m18.16187 31.334574l0 0c0 -3.6007233 2.9189625 -6.519684 6.5196857 -6.519684l0 0c1.7291279 0 3.387434 0.68689156 4.610113 1.9095707c1.2226772 1.2226772 1.9095707 2.8809853 1.9095707 4.610113l0 0c0 3.6007214 -2.9189625 6.5196857 -6.519684 6.5196857l0 0c-3.6007233 0 -6.5196857 -2.9189644 -6.5196857 -6.5196857z" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="2.0" stroke-linejoin="round" stroke-linecap="butt" d="m18.16187 31.334574l0 0c0 -3.6007233 2.9189625 -6.519684 6.5196857 -6.519684l0 0c1.7291279 0 3.387434 0.68689156 4.610113 1.9095707c1.2226772 1.2226772 1.9095707 2.8809853 1.9095707 4.610113l0 0c0 3.6007214 -2.9189625 6.5196857 -6.519684 6.5196857l0 0c-3.6007233 0 -6.5196857 -2.9189644 -6.5196857 -6.5196857z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m25.741367 37.241383l17.259842 17.259842" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m25.741367 37.241383l13.0172 13.017204" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m37.59062 51.426537l4.376869 2.040966l-2.040966 -4.376869z" fill-rule="evenodd"></path><path fill="#ffffff" d="m338.66934 81.90589l0 0c0 -3.7660446 3.0529785 -6.819031 6.819031 -6.819031l27.2753 0l0 0c1.8085327 0 3.5429688 0.71842957 4.821808 1.9972458c1.2788086 1.2788162 1.9972229 3.0132675 1.9972229 4.821785l0 206.17297c0 3.7660522 -3.0529785 6.819031 -6.819031 6.819031l-27.2753 0c-3.7660522 0 -6.819031 -3.0529785 -6.819031 -6.819031z" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m338.66934 81.90589l0 0c0 -3.7660446 3.0529785 -6.819031 6.819031 -6.819031l27.2753 0l0 0c1.8085327 0 3.5429688 0.71842957 4.821808 1.9972458c1.2788086 1.2788162 1.9972229 3.0132675 1.9972229 4.821785l0 206.17297c0 3.7660522 -3.0529785 6.819031 -6.819031 6.819031l-27.2753 0c-3.7660522 0 -6.819031 -3.0529785 -6.819031 -6.819031z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m394.1746 192.33089l58.2677 0l0 23.496063l-58.2677 0z" fill-rule="evenodd"></path><path fill="#000000" d="m403.1746 212.9915l2.484375 -8.875l0.84375 0l-2.484375 8.875l-0.84375 0zm3.7402344 -3.25q0 -1.734375 0.953125 -2.5625q0.796875 -0.6875 1.953125 -0.6875q1.28125 0 2.09375 0.84375q0.828125 0.828125 0.828125 2.3125q0 1.203125 -0.359375 1.890625q-0.359375 0.6875 -1.0625 1.078125q-0.6875 0.375 -1.5 0.375q-1.296875 0 -2.109375 -0.828125q-0.796875 -0.84375 -0.796875 -2.421875zm1.078125 0q0 1.1875 0.515625 1.78125q0.53125 0.59375 1.3125 0.59375q0.796875 0 1.3125 -0.59375q0.515625 -0.59375 0.515625 -1.8125q0 -1.15625 -0.53125 -1.75q-0.515625 -0.59375 -1.296875 -0.59375q-0.78125 0 -1.3125 0.59375q-0.515625 0.578125 -0.515625 1.78125zm10.064453 3.109375l0 -0.921875q-0.734375 1.0625 -1.984375 1.0625q-0.546875 0 -1.03125 -0.203125q-0.46875 -0.21875 -0.703125 -0.53125q-0.234375 -0.328125 -0.328125 -0.796875q-0.0625 -0.296875 -0.0625 -0.984375l0 -3.84375l1.0625 0l0 3.453125q0 0.8125 0.0625 1.109375q0.09375 0.40625 0.40625 0.65625q0.328125 0.234375 0.8125 0.234375q0.46875 0 0.875 -0.234375q0.421875 -0.25 0.59375 -0.671875q0.1875 -0.421875 0.1875 -1.21875l0 -3.328125l1.046875 0l0 6.21875l-0.9375 0zm4.892578 -0.9375l0.15625 0.921875q-0.453125 0.09375 -0.796875 0.09375q-0.578125 0 -0.890625 -0.171875q-0.3125 -0.1875 -0.453125 -0.484375q-0.125 -0.296875 -0.125 -1.25l0 -3.578125l-0.765625 0l0 -0.8125l0.765625 0l0 -1.546875l1.046875 -0.625l0 2.171875l1.0625 0l0 0.8125l-1.0625 0l0 3.640625q0 0.453125 0.046875 0.578125q0.0625 0.125 0.1875 0.203125q0.125 0.078125 0.359375 0.078125q0.1875 0 0.46875 -0.03125zm1.0371094 3.328125l0 -8.609375l0.953125 0l0 0.796875q0.34375 -0.46875 0.765625 -0.703125q0.4375 -0.234375 1.046875 -0.234375q0.796875 0 1.40625 0.40625q0.609375 0.40625 0.90625 1.15625q0.3125 0.75 0.3125 1.640625q0 0.953125 -0.34375 1.71875q-0.328125 0.765625 -0.984375 1.171875q-0.65625 0.40625 -1.375 0.40625q-0.53125 0 -0.953125 -0.21875q-0.421875 -0.234375 -0.6875 -0.5625l0 3.03125l-1.046875 0zm0.953125 -5.46875q0 1.203125 0.484375 1.78125q0.484375 0.5625 1.171875 0.5625q0.703125 0 1.203125 -0.59375q0.5 -0.59375 0.5 -1.84375q0 -1.1875 -0.484375 -1.765625q-0.484375 -0.59375 -1.171875 -0.59375q-0.671875 0 -1.1875 0.625q-0.515625 0.625 -0.515625 1.828125zm9.798828 3.078125l0 -0.921875q-0.734375 1.0625 -1.984375 1.0625q-0.546875 0 -1.03125 -0.203125q-0.46875 -0.21875 -0.703125 -0.53125q-0.234375 -0.328125 -0.328125 -0.796875q-0.0625 -0.296875 -0.0625 -0.984375l0 -3.84375l1.0625 0l0 3.453125q0 0.8125 0.0625 1.109375q0.09375 0.40625 0.40625 0.65625q0.328125 0.234375 0.8125 0.234375q0.46875 0 0.875 -0.234375q0.421875 -0.25 0.59375 -0.671875q0.1875 -0.421875 0.1875 -1.21875l0 -3.328125l1.046875 0l0 6.21875l-0.9375 0zm4.892578 -0.9375l0.15625 0.921875q-0.453125 0.09375 -0.796875 0.09375q-0.578125 0 -0.890625 -0.171875q-0.3125 -0.1875 -0.453125 -0.484375q-0.125 -0.296875 -0.125 -1.25l0 -3.578125l-0.765625 0l0 -0.8125l0.765625 0l0 -1.546875l1.046875 -0.625l0 2.171875l1.0625 0l0 0.8125l-1.0625 0l0 3.640625q0 0.453125 0.046875 0.578125q0.0625 0.125 0.1875 0.203125q0.125 0.078125 0.359375 0.078125q0.1875 0 0.46875 -0.03125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m467.42902 81.90589l0 0c0 -3.7660446 3.0529785 -6.819031 6.819031 -6.819031l27.27533 0l0 0c1.8085022 0 3.5429688 0.71842957 4.8217773 1.9972458c1.2788086 1.2788162 1.9972534 3.0132675 1.9972534 4.821785l0 78.86587c0 3.7660522 -3.053009 6.819031 -6.819031 6.819031l-27.27533 0c-3.7660522 0 -6.819031 -3.0529785 -6.819031 -6.819031z" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m467.42902 81.90589l0 0c0 -3.7660446 3.0529785 -6.819031 6.819031 -6.819031l27.27533 0l0 0c1.8085022 0 3.5429688 0.71842957 4.8217773 1.9972458c1.2788086 1.2788162 1.9972534 3.0132675 1.9972534 4.821785l0 78.86587c0 3.7660522 -3.053009 6.819031 -6.819031 6.819031l-27.27533 0c-3.7660522 0 -6.819031 -3.0529785 -6.819031 -6.819031z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m467.37534 101.249344l-87.93701 0.12598419" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m461.37534 101.257935l-81.93701 0.11739349" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m461.37772 102.90967l4.5357056 -1.658226l-4.5404663 -1.6452332z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m391.7599 73.883446l67.84253 0l0 23.496063l-67.84253 0z" fill-rule="evenodd"></path><path fill="#000000" d="m400.7599 94.544075l2.484375 -8.875l0.84375 0l-2.484375 8.875l-0.84375 0zm4.1308594 2.25l0 -8.609375l0.953125 0l0 0.796875q0.34375 -0.46875 0.765625 -0.703125q0.4375 -0.234375 1.046875 -0.234375q0.796875 0 1.40625 0.40625q0.609375 0.40625 0.90625 1.15625q0.3125 0.75 0.3125 1.640625q0 0.953125 -0.34375 1.71875q-0.328125 0.765625 -0.984375 1.171875q-0.65625 0.40625 -1.375 0.40625q-0.53125 0 -0.953125 -0.21875q-0.421875 -0.234375 -0.6875 -0.5625l0 3.03125l-1.046875 0zm0.953125 -5.46875q0 1.203125 0.484375 1.78125q0.484375 0.5625 1.171875 0.5625q0.703125 0 1.203125 -0.59375q0.5 -0.59375 0.5 -1.84375q0 -1.1875 -0.484375 -1.765625q-0.484375 -0.59375 -1.171875 -0.59375q-0.671875 0 -1.1875 0.625q-0.515625 0.625 -0.515625 1.828125zm5.705078 3.078125l0 -6.21875l0.953125 0l0 0.9375q0.359375 -0.65625 0.65625 -0.859375q0.3125 -0.21875 0.6875 -0.21875q0.53125 0 1.078125 0.328125l-0.359375 0.984375q-0.390625 -0.234375 -0.765625 -0.234375q-0.359375 0 -0.640625 0.21875q-0.265625 0.203125 -0.375 0.578125q-0.1875 0.5625 -0.1875 1.21875l0 3.265625l-1.046875 0zm3.6210938 -3.109375q0 -1.734375 0.953125 -2.5625q0.796875 -0.6875 1.953125 -0.6875q1.28125 0 2.09375 0.84375q0.828125 0.828125 0.828125 2.3125q0 1.203125 -0.359375 1.890625q-0.359375 0.6875 -1.0625 1.078125q-0.6875 0.375 -1.5 0.375q-1.296875 0 -2.109375 -0.828125q-0.796875 -0.84375 -0.796875 -2.421875zm1.078125 0q0 1.1875 0.515625 1.78125q0.53125 0.59375 1.3125 0.59375q0.796875 0 1.3125 -0.59375q0.515625 -0.59375 0.515625 -1.8125q0 -1.15625 -0.53125 -1.75q-0.515625 -0.59375 -1.296875 -0.59375q-0.78125 0 -1.3125 0.59375q-0.515625 0.578125 -0.515625 1.78125zm5.986328 3.109375l0 -6.21875l0.9375 0l0 0.875q0.296875 -0.46875 0.78125 -0.734375q0.484375 -0.28125 1.109375 -0.28125q0.6875 0 1.125 0.28125q0.453125 0.28125 0.625 0.796875q0.75 -1.078125 1.921875 -1.078125q0.9375 0 1.421875 0.515625q0.5 0.5 0.5 1.578125l0 4.265625l-1.046875 0l0 -3.921875q0 -0.625 -0.109375 -0.90625q-0.09375 -0.28125 -0.359375 -0.453125q-0.265625 -0.171875 -0.640625 -0.171875q-0.65625 0 -1.09375 0.4375q-0.421875 0.4375 -0.421875 1.40625l0 3.609375l-1.0625 0l0 -4.046875q0 -0.703125 -0.265625 -1.046875q-0.25 -0.359375 -0.828125 -0.359375q-0.453125 0 -0.828125 0.234375q-0.375 0.234375 -0.546875 0.6875q-0.171875 0.453125 -0.171875 1.296875l0 3.234375l-1.046875 0zm9.996094 2.390625l0 -8.609375l0.953125 0l0 0.796875q0.34375 -0.46875 0.765625 -0.703125q0.4375 -0.234375 1.046875 -0.234375q0.796875 0 1.40625 0.40625q0.609375 0.40625 0.90625 1.15625q0.3125 0.75 0.3125 1.640625q0 0.953125 -0.34375 1.71875q-0.328125 0.765625 -0.984375 1.171875q-0.65625 0.40625 -1.375 0.40625q-0.53125 0 -0.953125 -0.21875q-0.421875 -0.234375 -0.6875 -0.5625l0 3.03125l-1.046875 0zm0.953125 -5.46875q0 1.203125 0.484375 1.78125q0.484375 0.5625 1.171875 0.5625q0.703125 0 1.203125 -0.59375q0.5 -0.59375 0.5 -1.84375q0 -1.1875 -0.484375 -1.765625q-0.484375 -0.59375 -1.171875 -0.59375q-0.671875 0 -1.1875 0.625q-0.515625 0.625 -0.515625 1.828125zm8.017578 2.140625l0.15625 0.921875q-0.453125 0.09375 -0.796875 0.09375q-0.578125 0 -0.890625 -0.171875q-0.3125 -0.1875 -0.453125 -0.484375q-0.125 -0.296875 -0.125 -1.25l0 -3.578125l-0.765625 0l0 -0.8125l0.765625 0l0 -1.546875l1.046875 -0.625l0 2.171875l1.0625 0l0 0.8125l-1.0625 0l0 3.640625q0 0.453125 0.046875 0.578125q0.0625 0.125 0.1875 0.203125q0.125 0.078125 0.359375 0.078125q0.1875 0 0.46875 -0.03125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m389.18277 102.63082l81.85828 0l0 23.496063l-81.85828 0z" fill-rule="evenodd"></path><path fill="#000000" d="m400.62027 123.150826l-1.640625 0l0 -6.21875l1.53125 0l0 0.875q0.390625 -0.625 0.703125 -0.8125q0.3125 -0.203125 0.703125 -0.203125q0.5625 0 1.09375 0.3125l-0.515625 1.421875q-0.421875 -0.265625 -0.765625 -0.265625q-0.359375 0 -0.59375 0.203125q-0.234375 0.1875 -0.375 0.6875q-0.140625 0.484375 -0.140625 2.078125l0 1.921875zm6.701172 -1.984375l1.640625 0.28125q-0.3125 0.90625 -1.0 1.375q-0.6875 0.46875 -1.703125 0.46875q-1.625 0 -2.40625 -1.0625q-0.625 -0.84375 -0.625 -2.140625q0 -1.546875 0.8125 -2.421875q0.8125 -0.875 2.046875 -0.875q1.390625 0 2.1875 0.921875q0.8125 0.90625 0.765625 2.796875l-4.125 0q0.03125 0.734375 0.40625 1.140625q0.375 0.40625 0.953125 0.40625q0.375 0 0.640625 -0.203125q0.265625 -0.21875 0.40625 -0.6875zm0.09375 -1.65625q-0.015625 -0.71875 -0.375 -1.09375q-0.34375 -0.375 -0.859375 -0.375q-0.53125 0 -0.890625 0.390625q-0.34375 0.40625 -0.34375 1.078125l2.46875 0zm2.3925781 1.859375l1.65625 -0.25q0.109375 0.484375 0.421875 0.734375q0.328125 0.25 0.90625 0.25q0.640625 0 0.953125 -0.234375q0.21875 -0.171875 0.21875 -0.4375q0 -0.1875 -0.109375 -0.3125q-0.125 -0.125 -0.546875 -0.21875q-2.0 -0.4375 -2.53125 -0.796875q-0.734375 -0.515625 -0.734375 -1.40625q0 -0.8125 0.625 -1.359375q0.640625 -0.546875 1.984375 -0.546875q1.28125 0 1.90625 0.421875q0.625 0.40625 0.859375 1.21875l-1.5625 0.28125q-0.09375 -0.359375 -0.375 -0.546875q-0.28125 -0.203125 -0.796875 -0.203125q-0.640625 0 -0.921875 0.1875q-0.1875 0.125 -0.1875 0.328125q0 0.1875 0.15625 0.3125q0.21875 0.15625 1.53125 0.453125q1.328125 0.296875 1.84375 0.734375q0.515625 0.4375 0.515625 1.21875q0 0.859375 -0.71875 1.484375q-0.703125 0.609375 -2.109375 0.609375q-1.265625 0 -2.015625 -0.515625q-0.734375 -0.515625 -0.96875 -1.40625zm7.205078 -4.4375l1.53125 0l0 0.90625q0.3125 -0.46875 0.8125 -0.75q0.515625 -0.296875 1.140625 -0.296875q1.078125 0 1.828125 0.84375q0.765625 0.84375 0.765625 2.375q0 1.546875 -0.765625 2.421875q-0.765625 0.859375 -1.84375 0.859375q-0.515625 0 -0.9375 -0.203125q-0.421875 -0.203125 -0.875 -0.703125l0 3.140625l-1.65625 0l0 -8.59375zm1.625 3.0q0 1.046875 0.421875 1.546875q0.421875 0.5 1.015625 0.5q0.578125 0 0.953125 -0.453125q0.375 -0.46875 0.375 -1.515625q0 -0.96875 -0.390625 -1.4375q-0.390625 -0.484375 -0.96875 -0.484375q-0.609375 0 -1.015625 0.46875q-0.390625 0.46875 -0.390625 1.375zm5.376953 0.015625q0 -0.8125 0.40625 -1.578125q0.40625 -0.78125 1.140625 -1.171875q0.734375 -0.40625 1.65625 -0.40625q1.40625 0 2.3125 0.921875q0.90625 0.90625 0.90625 2.3125q0 1.40625 -0.921875 2.34375q-0.90625 0.921875 -2.28125 0.921875q-0.859375 0 -1.640625 -0.390625q-0.765625 -0.390625 -1.171875 -1.125q-0.40625 -0.75 -0.40625 -1.828125zm1.6875 0.09375q0 0.921875 0.4375 1.421875q0.4375 0.484375 1.078125 0.484375q0.65625 0 1.078125 -0.484375q0.4375 -0.5 0.4375 -1.4375q0 -0.90625 -0.4375 -1.390625q-0.421875 -0.5 -1.078125 -0.5q-0.640625 0 -1.078125 0.5q-0.4375 0.484375 -0.4375 1.40625zm11.673828 3.109375l-1.640625 0l0 -3.171875q0 -1.015625 -0.109375 -1.3125q-0.09375 -0.296875 -0.34375 -0.453125q-0.234375 -0.171875 -0.5625 -0.171875q-0.4375 0 -0.78125 0.234375q-0.328125 0.234375 -0.453125 0.625q-0.125 0.390625 -0.125 1.4375l0 2.8125l-1.65625 0l0 -6.21875l1.53125 0l0 0.90625q0.8125 -1.046875 2.0625 -1.046875q0.53125 0 0.984375 0.203125q0.453125 0.1875 0.6875 0.5q0.234375 0.296875 0.3125 0.6875q0.09375 0.375 0.09375 1.09375l0 3.875zm7.376953 0l-1.515625 0l0 -0.921875q-0.390625 0.546875 -0.90625 0.8125q-0.515625 0.25 -1.046875 0.25q-1.078125 0 -1.84375 -0.859375q-0.75 -0.875 -0.75 -2.421875q0 -1.578125 0.734375 -2.390625q0.75 -0.828125 1.890625 -0.828125q1.03125 0 1.796875 0.859375l0 -3.09375l1.640625 0l0 8.59375zm-4.390625 -3.25q0 1.0 0.28125 1.4375q0.390625 0.65625 1.109375 0.65625q0.5625 0 0.953125 -0.484375q0.40625 -0.484375 0.40625 -1.453125q0 -1.0625 -0.390625 -1.53125q-0.375 -0.484375 -0.984375 -0.484375q-0.578125 0 -0.984375 0.46875q-0.390625 0.46875 -0.390625 1.390625z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m467.37534 129.75066l-87.653564 0.12599182" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m467.37534 129.75066l-81.653564 0.11735535" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m385.7194 128.2163l-4.5357056 1.6582489l4.5404663 1.6452026z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m79.83723 81.90589l0 0c0 -3.7660446 3.0529861 -6.819031 6.8190384 -6.819031l27.275314 0l0 0c1.8085251 0 3.5429688 0.71842957 4.821785 1.9972458c1.2788162 1.2788162 1.9972534 3.0132675 1.9972534 4.821785l0 154.64539c0 3.7660522 -3.0529861 6.819046 -6.8190384 6.819046l-27.275314 0c-3.7660522 0 -6.8190384 -3.0529938 -6.8190384 -6.819046z" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m79.83723 81.90589l0 0c0 -3.7660446 3.0529861 -6.819031 6.8190384 -6.819031l27.275314 0l0 0c1.8085251 0 3.5429688 0.71842957 4.821785 1.9972458c1.2788162 1.2788162 1.9972534 3.0132675 1.9972534 4.821785l0 154.64539c0 3.7660522 -3.0529861 6.819046 -6.8190384 6.819046l-27.275314 0c-3.7660522 0 -6.8190384 -3.0529938 -6.8190384 -6.819046z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m202.15942 157.14264l70.99213 0l0 23.496063l-70.99213 0z" fill-rule="evenodd"></path><path fill="#000000" d="m211.15942 177.80325l2.484375 -8.875l0.84375 0l-2.484375 8.875l-0.84375 0zm8.380859 -2.140625l1.09375 0.125q-0.25 0.953125 -0.953125 1.484375q-0.703125 0.53125 -1.78125 0.53125q-1.359375 0 -2.171875 -0.84375q-0.796875 -0.84375 -0.796875 -2.359375q0 -1.5625 0.8125 -2.421875q0.8125 -0.875 2.09375 -0.875q1.25 0 2.03125 0.84375q0.796875 0.84375 0.796875 2.390625q0 0.09375 0 0.28125l-4.640625 0q0.0625 1.03125 0.578125 1.578125q0.515625 0.53125 1.296875 0.53125q0.578125 0 0.984375 -0.296875q0.421875 -0.3125 0.65625 -0.96875zm-3.453125 -1.703125l3.46875 0q-0.0625 -0.796875 -0.390625 -1.1875q-0.515625 -0.609375 -1.3125 -0.609375q-0.734375 0 -1.234375 0.484375q-0.484375 0.484375 -0.53125 1.3125zm5.861328 3.703125l0 -6.21875l0.953125 0l0 0.9375q0.359375 -0.65625 0.65625 -0.859375q0.3125 -0.21875 0.6875 -0.21875q0.53125 0 1.078125 0.328125l-0.359375 0.984375q-0.390625 -0.234375 -0.765625 -0.234375q-0.359375 0 -0.640625 0.21875q-0.265625 0.203125 -0.375 0.578125q-0.1875 0.5625 -0.1875 1.21875l0 3.265625l-1.046875 0zm3.9960938 0l0 -6.21875l0.953125 0l0 0.9375q0.359375 -0.65625 0.65625 -0.859375q0.3125 -0.21875 0.6875 -0.21875q0.53125 0 1.078125 0.328125l-0.359375 0.984375q-0.390625 -0.234375 -0.765625 -0.234375q-0.359375 0 -0.640625 0.21875q-0.265625 0.203125 -0.375 0.578125q-0.1875 0.5625 -0.1875 1.21875l0 3.265625l-1.046875 0zm3.6210938 -3.109375q0 -1.734375 0.953125 -2.5625q0.796875 -0.6875 1.953125 -0.6875q1.28125 0 2.09375 0.84375q0.828125 0.828125 0.828125 2.3125q0 1.203125 -0.359375 1.890625q-0.359375 0.6875 -1.0625 1.078125q-0.6875 0.375 -1.5 0.375q-1.296875 0 -2.109375 -0.828125q-0.796875 -0.84375 -0.796875 -2.421875zm1.078125 0q0 1.1875 0.515625 1.78125q0.53125 0.59375 1.3125 0.59375q0.796875 0 1.3125 -0.59375q0.515625 -0.59375 0.515625 -1.8125q0 -1.15625 -0.53125 -1.75q-0.515625 -0.59375 -1.296875 -0.59375q-0.78125 0 -1.3125 0.59375q-0.515625 0.578125 -0.515625 1.78125zm5.970703 3.109375l0 -6.21875l0.953125 0l0 0.9375q0.359375 -0.65625 0.65625 -0.859375q0.3125 -0.21875 0.6875 -0.21875q0.53125 0 1.078125 0.328125l-0.359375 0.984375q-0.390625 -0.234375 -0.765625 -0.234375q-0.359375 0 -0.640625 0.21875q-0.265625 0.203125 -0.375 0.578125q-0.1875 0.5625 -0.1875 1.21875l0 3.265625l-1.046875 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m338.58798 156.91757l-217.63782 -0.03149414" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m338.58798 156.91757l-211.63782 -0.03062439" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m126.95041 155.23521l-4.5383377 1.6510773l4.5378647 1.6523895z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m198.3491 129.38345l67.842514 0l0 23.496063l-67.842514 0z" fill-rule="evenodd"></path><path fill="#000000" d="m207.3491 150.04407l2.484375 -8.875l0.84375 0l-2.484375 8.875l-0.84375 0zm4.3808594 -0.140625l0 -5.40625l-0.9375 0l0 -0.8125l0.9375 0l0 -0.671875q0 -0.625 0.109375 -0.921875q0.15625 -0.421875 0.53125 -0.671875q0.390625 -0.25 1.078125 -0.25q0.453125 0 0.984375 0.109375l-0.15625 0.90625q-0.328125 -0.046875 -0.625 -0.046875q-0.484375 0 -0.6875 0.203125q-0.1875 0.203125 -0.1875 0.765625l0 0.578125l1.21875 0l0 0.8125l-1.21875 0l0 5.40625l-1.046875 0zm7.1464844 -0.765625q-0.59375 0.5 -1.140625 0.703125q-0.53125 0.203125 -1.15625 0.203125q-1.03125 0 -1.578125 -0.5q-0.546875 -0.5 -0.546875 -1.28125q0 -0.453125 0.203125 -0.828125q0.203125 -0.390625 0.546875 -0.609375q0.34375 -0.234375 0.765625 -0.34375q0.296875 -0.09375 0.9375 -0.171875q1.265625 -0.140625 1.875 -0.359375q0 -0.21875 0 -0.265625q0 -0.65625 -0.296875 -0.921875q-0.40625 -0.34375 -1.203125 -0.34375q-0.734375 0 -1.09375 0.265625q-0.359375 0.25 -0.53125 0.90625l-1.03125 -0.140625q0.140625 -0.65625 0.46875 -1.0625q0.328125 -0.40625 0.9375 -0.625q0.609375 -0.21875 1.40625 -0.21875q0.796875 0 1.296875 0.1875q0.5 0.1875 0.734375 0.46875q0.234375 0.28125 0.328125 0.71875q0.046875 0.265625 0.046875 0.96875l0 1.40625q0 1.46875 0.0625 1.859375q0.078125 0.390625 0.28125 0.75l-1.109375 0q-0.15625 -0.328125 -0.203125 -0.765625zm-0.09375 -2.359375q-0.578125 0.234375 -1.71875 0.40625q-0.65625 0.09375 -0.921875 0.21875q-0.265625 0.109375 -0.421875 0.328125q-0.140625 0.21875 -0.140625 0.5q0 0.421875 0.3125 0.703125q0.328125 0.28125 0.9375 0.28125q0.609375 0 1.078125 -0.265625q0.484375 -0.265625 0.703125 -0.734375q0.171875 -0.359375 0.171875 -1.046875l0 -0.390625zm2.7050781 -4.25l0 -1.21875l1.0625 0l0 1.21875l-1.0625 0zm0 7.375l0 -6.21875l1.0625 0l0 6.21875l-1.0625 0zm2.6347656 0l0 -8.59375l1.0625 0l0 8.59375l-1.0625 0zm6.7753906 0l0 -0.921875q-0.734375 1.0625 -1.984375 1.0625q-0.546875 0 -1.03125 -0.203125q-0.46875 -0.21875 -0.703125 -0.53125q-0.234375 -0.328125 -0.328125 -0.796875q-0.0625 -0.296875 -0.0625 -0.984375l0 -3.84375l1.0625 0l0 3.453125q0 0.8125 0.0625 1.109375q0.09375 0.40625 0.40625 0.65625q0.328125 0.234375 0.8125 0.234375q0.46875 0 0.875 -0.234375q0.421875 -0.25 0.59375 -0.671875q0.1875 -0.421875 0.1875 -1.21875l0 -3.328125l1.046875 0l0 6.21875l-0.9375 0zm2.5800781 0l0 -6.21875l0.953125 0l0 0.9375q0.359375 -0.65625 0.65625 -0.859375q0.3125 -0.21875 0.6875 -0.21875q0.53125 0 1.078125 0.328125l-0.359375 0.984375q-0.390625 -0.234375 -0.765625 -0.234375q-0.359375 0 -0.640625 0.21875q-0.265625 0.203125 -0.375 0.578125q-0.1875 0.5625 -0.1875 1.21875l0 3.265625l-1.046875 0zm8.261719 -2.0l1.09375 0.125q-0.25 0.953125 -0.953125 1.484375q-0.703125 0.53125 -1.78125 0.53125q-1.359375 0 -2.171875 -0.84375q-0.796875 -0.84375 -0.796875 -2.359375q0 -1.5625 0.8125 -2.421875q0.8125 -0.875 2.09375 -0.875q1.25 0 2.03125 0.84375q0.796875 0.84375 0.796875 2.390625q0 0.09375 0 0.28125l-4.640625 0q0.0625 1.03125 0.578125 1.578125q0.515625 0.53125 1.296875 0.53125q0.578125 0 0.984375 -0.296875q0.421875 -0.3125 0.65625 -0.96875zm-3.453125 -1.703125l3.46875 0q-0.0625 -0.796875 -0.390625 -1.1875q-0.515625 -0.609375 -1.3125 -0.609375q-0.734375 0 -1.234375 0.484375q-0.484375 0.484375 -0.53125 1.3125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m178.32155 102.883446l104.88188 0l0 23.496063l-104.88188 0z" fill-rule="evenodd"></path><path fill="#000000" d="m187.32155 123.544075l2.484375 -8.875l0.84375 0l-2.484375 8.875l-0.84375 0zm3.7089844 -2.0l1.03125 -0.15625q0.09375 0.625 0.484375 0.953125q0.40625 0.328125 1.140625 0.328125q0.71875 0 1.0625 -0.28125q0.359375 -0.296875 0.359375 -0.703125q0 -0.359375 -0.3125 -0.5625q-0.21875 -0.140625 -1.078125 -0.359375q-1.15625 -0.296875 -1.609375 -0.5q-0.4375 -0.21875 -0.671875 -0.59375q-0.234375 -0.375 -0.234375 -0.84375q0 -0.40625 0.1875 -0.765625q0.1875 -0.359375 0.515625 -0.59375q0.25 -0.171875 0.671875 -0.296875q0.421875 -0.125 0.921875 -0.125q0.71875 0 1.265625 0.21875q0.5625 0.203125 0.828125 0.5625q0.265625 0.359375 0.359375 0.953125l-1.03125 0.140625q-0.0625 -0.46875 -0.40625 -0.734375q-0.328125 -0.28125 -0.953125 -0.28125q-0.71875 0 -1.03125 0.25q-0.3125 0.234375 -0.3125 0.5625q0 0.203125 0.125 0.359375q0.140625 0.171875 0.40625 0.28125q0.15625 0.0625 0.9375 0.265625q1.125 0.3125 1.5625 0.5q0.4375 0.1875 0.6875 0.546875q0.25 0.359375 0.25 0.90625q0 0.53125 -0.3125 1.0q-0.296875 0.453125 -0.875 0.71875q-0.578125 0.25 -1.3125 0.25q-1.21875 0 -1.859375 -0.5q-0.625 -0.515625 -0.796875 -1.5zm10.5 1.859375l0 -0.921875q-0.734375 1.0625 -1.984375 1.0625q-0.546875 0 -1.03125 -0.203125q-0.46875 -0.21875 -0.703125 -0.53125q-0.234375 -0.328125 -0.328125 -0.796875q-0.0625 -0.296875 -0.0625 -0.984375l0 -3.84375l1.0625 0l0 3.453125q0 0.8125 0.0625 1.109375q0.09375 0.40625 0.40625 0.65625q0.328125 0.234375 0.8125 0.234375q0.46875 0 0.875 -0.234375q0.421875 -0.25 0.59375 -0.671875q0.1875 -0.421875 0.1875 -1.21875l0 -3.328125l1.046875 0l0 6.21875l-0.9375 0zm6.658203 -2.28125l1.03125 0.140625q-0.171875 1.0625 -0.875 1.671875q-0.703125 0.609375 -1.71875 0.609375q-1.28125 0 -2.0625 -0.828125q-0.765625 -0.84375 -0.765625 -2.40625q0 -1.0 0.328125 -1.75q0.34375 -0.765625 1.015625 -1.140625q0.6875 -0.375 1.5 -0.375q1.0 0 1.640625 0.515625q0.65625 0.5 0.84375 1.453125l-1.03125 0.15625q-0.140625 -0.625 -0.515625 -0.9375q-0.375 -0.328125 -0.90625 -0.328125q-0.796875 0 -1.296875 0.578125q-0.5 0.5625 -0.5 1.796875q0 1.265625 0.484375 1.828125q0.484375 0.5625 1.25 0.5625q0.625 0 1.03125 -0.375q0.421875 -0.375 0.546875 -1.171875zm6.0 0l1.03125 0.140625q-0.171875 1.0625 -0.875 1.671875q-0.703125 0.609375 -1.71875 0.609375q-1.28125 0 -2.0625 -0.828125q-0.765625 -0.84375 -0.765625 -2.40625q0 -1.0 0.328125 -1.75q0.34375 -0.765625 1.015625 -1.140625q0.6875 -0.375 1.5 -0.375q1.0 0 1.640625 0.515625q0.65625 0.5 0.84375 1.453125l-1.03125 0.15625q-0.140625 -0.625 -0.515625 -0.9375q-0.375 -0.328125 -0.90625 -0.328125q-0.796875 0 -1.296875 0.578125q-0.5 0.5625 -0.5 1.796875q0 1.265625 0.484375 1.828125q0.484375 0.5625 1.25 0.5625q0.625 0 1.03125 -0.375q0.421875 -0.375 0.546875 -1.171875zm6.1875 0.28125l1.09375 0.125q-0.25 0.953125 -0.953125 1.484375q-0.703125 0.53125 -1.78125 0.53125q-1.359375 0 -2.171875 -0.84375q-0.796875 -0.84375 -0.796875 -2.359375q0 -1.5625 0.8125 -2.421875q0.8125 -0.875 2.09375 -0.875q1.25 0 2.03125 0.84375q0.796875 0.84375 0.796875 2.390625q0 0.09375 0 0.28125l-4.640625 0q0.0625 1.03125 0.578125 1.578125q0.515625 0.53125 1.296875 0.53125q0.578125 0 0.984375 -0.296875q0.421875 -0.3125 0.65625 -0.96875zm-3.453125 -1.703125l3.46875 0q-0.0625 -0.796875 -0.390625 -1.1875q-0.515625 -0.609375 -1.3125 -0.609375q-0.734375 0 -1.234375 0.484375q-0.484375 0.484375 -0.53125 1.3125zm5.455078 1.84375l1.03125 -0.15625q0.09375 0.625 0.484375 0.953125q0.40625 0.328125 1.140625 0.328125q0.71875 0 1.0625 -0.28125q0.359375 -0.296875 0.359375 -0.703125q0 -0.359375 -0.3125 -0.5625q-0.21875 -0.140625 -1.078125 -0.359375q-1.15625 -0.296875 -1.609375 -0.5q-0.4375 -0.21875 -0.671875 -0.59375q-0.234375 -0.375 -0.234375 -0.84375q0 -0.40625 0.1875 -0.765625q0.1875 -0.359375 0.515625 -0.59375q0.25 -0.171875 0.671875 -0.296875q0.421875 -0.125 0.921875 -0.125q0.71875 0 1.265625 0.21875q0.5625 0.203125 0.828125 0.5625q0.265625 0.359375 0.359375 0.953125l-1.03125 0.140625q-0.0625 -0.46875 -0.40625 -0.734375q-0.328125 -0.28125 -0.953125 -0.28125q-0.71875 0 -1.03125 0.25q-0.3125 0.234375 -0.3125 0.5625q0 0.203125 0.125 0.359375q0.140625 0.171875 0.40625 0.28125q0.15625 0.0625 0.9375 0.265625q1.125 0.3125 1.5625 0.5q0.4375 0.1875 0.6875 0.546875q0.25 0.359375 0.25 0.90625q0 0.53125 -0.3125 1.0q-0.296875 0.453125 -0.875 0.71875q-0.578125 0.25 -1.3125 0.25q-1.21875 0 -1.859375 -0.5q-0.625 -0.515625 -0.796875 -1.5zm6.0 0l1.03125 -0.15625q0.09375 0.625 0.484375 0.953125q0.40625 0.328125 1.140625 0.328125q0.71875 0 1.0625 -0.28125q0.359375 -0.296875 0.359375 -0.703125q0 -0.359375 -0.3125 -0.5625q-0.21875 -0.140625 -1.078125 -0.359375q-1.15625 -0.296875 -1.609375 -0.5q-0.4375 -0.21875 -0.671875 -0.59375q-0.234375 -0.375 -0.234375 -0.84375q0 -0.40625 0.1875 -0.765625q0.1875 -0.359375 0.515625 -0.59375q0.25 -0.171875 0.671875 -0.296875q0.421875 -0.125 0.921875 -0.125q0.71875 0 1.265625 0.21875q0.5625 0.203125 0.828125 0.5625q0.265625 0.359375 0.359375 0.953125l-1.03125 0.140625q-0.0625 -0.46875 -0.40625 -0.734375q-0.328125 -0.28125 -0.953125 -0.28125q-0.71875 0 -1.03125 0.25q-0.3125 0.234375 -0.3125 0.5625q0 0.203125 0.125 0.359375q0.140625 0.171875 0.40625 0.28125q0.15625 0.0625 0.9375 0.265625q1.125 0.3125 1.5625 0.5q0.4375 0.1875 0.6875 0.546875q0.25 0.359375 0.25 0.90625q0 0.53125 -0.3125 1.0q-0.296875 0.453125 -0.875 0.71875q-0.578125 0.25 -1.3125 0.25q-1.21875 0 -1.859375 -0.5q-0.625 -0.515625 -0.796875 -1.5zm8.4375 4.390625q-0.875 -1.109375 -1.484375 -2.578125q-0.59375 -1.484375 -0.59375 -3.0625q0 -1.390625 0.4375 -2.671875q0.53125 -1.484375 1.640625 -2.953125l0.75 0q-0.703125 1.21875 -0.9375 1.734375q-0.359375 0.8125 -0.5625 1.6875q-0.25 1.09375 -0.25 2.203125q0 2.828125 1.75 5.640625l-0.75 0zm2.2304688 -2.53125l0 -5.40625l-0.9375 0l0 -0.8125l0.9375 0l0 -0.671875q0 -0.625 0.109375 -0.921875q0.15625 -0.421875 0.53125 -0.671875q0.390625 -0.25 1.078125 -0.25q0.453125 0 0.984375 0.109375l-0.15625 0.90625q-0.328125 -0.046875 -0.625 -0.046875q-0.484375 0 -0.6875 0.203125q-0.1875 0.203125 -0.1875 0.765625l0 0.578125l1.21875 0l0 0.8125l-1.21875 0l0 5.40625l-1.046875 0zm7.1464844 -0.765625q-0.59375 0.5 -1.140625 0.703125q-0.53125 0.203125 -1.15625 0.203125q-1.03125 0 -1.578125 -0.5q-0.546875 -0.5 -0.546875 -1.28125q0 -0.453125 0.203125 -0.828125q0.203125 -0.390625 0.546875 -0.609375q0.34375 -0.234375 0.765625 -0.34375q0.296875 -0.09375 0.9375 -0.171875q1.265625 -0.140625 1.875 -0.359375q0 -0.21875 0 -0.265625q0 -0.65625 -0.296875 -0.921875q-0.40625 -0.34375 -1.203125 -0.34375q-0.734375 0 -1.09375 0.265625q-0.359375 0.25 -0.53125 0.90625l-1.03125 -0.140625q0.140625 -0.65625 0.46875 -1.0625q0.328125 -0.40625 0.9375 -0.625q0.609375 -0.21875 1.40625 -0.21875q0.796875 0 1.296875 0.1875q0.5 0.1875 0.734375 0.46875q0.234375 0.28125 0.328125 0.71875q0.046875 0.265625 0.046875 0.96875l0 1.40625q0 1.46875 0.0625 1.859375q0.078125 0.390625 0.28125 0.75l-1.109375 0q-0.15625 -0.328125 -0.203125 -0.765625zm-0.09375 -2.359375q-0.578125 0.234375 -1.71875 0.40625q-0.65625 0.09375 -0.921875 0.21875q-0.265625 0.109375 -0.421875 0.328125q-0.140625 0.21875 -0.140625 0.5q0 0.421875 0.3125 0.703125q0.328125 0.28125 0.9375 0.28125q0.609375 0 1.078125 -0.265625q0.484375 -0.265625 0.703125 -0.734375q0.171875 -0.359375 0.171875 -1.046875l0 -0.390625zm2.6738281 3.125l0 -8.59375l1.0625 0l0 8.59375l-1.0625 0zm2.2753906 -1.859375l1.03125 -0.15625q0.09375 0.625 0.484375 0.953125q0.40625 0.328125 1.140625 0.328125q0.71875 0 1.0625 -0.28125q0.359375 -0.296875 0.359375 -0.703125q0 -0.359375 -0.3125 -0.5625q-0.21875 -0.140625 -1.078125 -0.359375q-1.15625 -0.296875 -1.609375 -0.5q-0.4375 -0.21875 -0.671875 -0.59375q-0.234375 -0.375 -0.234375 -0.84375q0 -0.40625 0.1875 -0.765625q0.1875 -0.359375 0.515625 -0.59375q0.25 -0.171875 0.671875 -0.296875q0.421875 -0.125 0.921875 -0.125q0.71875 0 1.265625 0.21875q0.5625 0.203125 0.828125 0.5625q0.265625 0.359375 0.359375 0.953125l-1.03125 0.140625q-0.0625 -0.46875 -0.40625 -0.734375q-0.328125 -0.28125 -0.953125 -0.28125q-0.71875 0 -1.03125 0.25q-0.3125 0.234375 -0.3125 0.5625q0 0.203125 0.125 0.359375q0.140625 0.171875 0.40625 0.28125q0.15625 0.0625 0.9375 0.265625q1.125 0.3125 1.5625 0.5q0.4375 0.1875 0.6875 0.546875q0.24998474 0.359375 0.24998474 0.90625q0 0.53125 -0.31248474 1.0q-0.296875 0.453125 -0.875 0.71875q-0.578125 0.25 -1.3125 0.25q-1.21875 0 -1.859375 -0.5q-0.625 -0.515625 -0.796875 -1.5zm10.67186 -0.140625l1.09375 0.125q-0.25 0.953125 -0.953125 1.484375q-0.703125 0.53125 -1.78125 0.53125q-1.359375 0 -2.171875 -0.84375q-0.796875 -0.84375 -0.796875 -2.359375q0 -1.5625 0.8125 -2.421875q0.8125 -0.875 2.09375 -0.875q1.25 0 2.03125 0.84375q0.796875 0.84375 0.796875 2.390625q0 0.09375 0 0.28125l-4.640625 0q0.0625 1.03125 0.578125 1.578125q0.515625 0.53125 1.296875 0.53125q0.578125 0 0.984375 -0.296875q0.421875 -0.3125 0.65625 -0.96875zm-3.453125 -1.703125l3.46875 0q-0.0625 -0.796875 -0.390625 -1.1875q-0.515625 -0.609375 -1.3125 -0.609375q-0.734375 0 -1.234375 0.484375q-0.484375 0.484375 -0.53125 1.3125zm6.564453 6.234375l-0.75 0q1.75 -2.8125 1.75 -5.640625q0 -1.09375 -0.25 -2.1875q-0.203125 -0.875 -0.5625 -1.6875q-0.234375 -0.515625 -0.9375 -1.75l0.75 0q1.09375 1.46875 1.625 2.953125q0.453125 1.28125 0.453125 2.671875q0 1.578125 -0.609375 3.0625q-0.609375 1.46875 -1.46875 2.578125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m338.06534 129.88124l-217.10237 0" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m338.06534 129.88124l-211.10237 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m126.96297 128.2295l-4.5380936 1.6517334l4.5380936 1.6517181z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m338.66934 184.87952l-218.58269 0" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m338.66934 184.87952l-212.58269 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m126.086655 183.22778l-4.5380936 1.6517334l4.5380936 1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m206.78107 74.3782l70.99213 0l0 27.496063l-70.99213 0z" fill-rule="evenodd"></path><path fill="#000000" d="m217.87482 90.570076l-1.5 -0.265625q0.25 -0.90625 0.859375 -1.328125q0.625 -0.4375 1.84375 -0.4375q1.09375 0 1.625 0.265625q0.546875 0.25 0.765625 0.65625q0.21875 0.390625 0.21875 1.46875l-0.015625 1.921875q0 0.828125 0.078125 1.21875q0.078125 0.375 0.296875 0.828125l-1.625 0q-0.0625 -0.171875 -0.15625 -0.484375q-0.046875 -0.15625 -0.0625 -0.203125q-0.421875 0.421875 -0.90625 0.625q-0.46875 0.203125 -1.015625 0.203125q-0.96875 0 -1.53125 -0.515625q-0.546875 -0.53125 -0.546875 -1.328125q0 -0.53125 0.25 -0.9375q0.265625 -0.40625 0.71875 -0.625q0.453125 -0.234375 1.3125 -0.390625q1.140625 -0.21875 1.59375 -0.40625l0 -0.15625q0 -0.484375 -0.234375 -0.6875q-0.234375 -0.203125 -0.890625 -0.203125q-0.4375 0 -0.6875 0.171875q-0.234375 0.171875 -0.390625 0.609375zm2.203125 1.34375q-0.3125 0.09375 -1.0 0.25q-0.6875 0.140625 -0.90625 0.28125q-0.3125 0.234375 -0.3125 0.578125q0 0.34375 0.25 0.609375q0.265625 0.25 0.65625 0.25q0.453125 0 0.859375 -0.296875q0.296875 -0.21875 0.390625 -0.546875q0.0625 -0.203125 0.0625 -0.796875l0 -0.328125zm2.6582031 1.203125l1.65625 -0.25q0.109375 0.484375 0.421875 0.734375q0.328125 0.25 0.90625 0.25q0.640625 0 0.953125 -0.234375q0.21875 -0.171875 0.21875 -0.4375q0 -0.1875 -0.109375 -0.3125q-0.125 -0.125 -0.546875 -0.21875q-2.0 -0.4375 -2.53125 -0.796875q-0.734375 -0.515625 -0.734375 -1.40625q0 -0.8125 0.625 -1.359375q0.640625 -0.546875 1.984375 -0.546875q1.28125 0 1.90625 0.421875q0.625 0.40625 0.859375 1.21875l-1.5625 0.28125q-0.09375 -0.359375 -0.375 -0.546875q-0.28125 -0.203125 -0.796875 -0.203125q-0.640625 0 -0.921875 0.1875q-0.1875 0.125 -0.1875 0.328125q0 0.1875 0.15625 0.3125q0.21875 0.15625 1.53125 0.453125q1.328125 0.296875 1.84375 0.734375q0.515625 0.4375 0.515625 1.21875q0 0.859375 -0.71875 1.484375q-0.703125 0.609375 -2.109375 0.609375q-1.265625 0 -2.015625 -0.515625q-0.734375 -0.515625 -0.96875 -1.40625zm7.189453 1.78125l0 -8.59375l1.65625 0l0 4.5625l1.921875 -2.1875l2.03125 0l-2.125 2.265625l2.28125 3.953125l-1.78125 0l-1.5625 -2.796875l-0.765625 0.796875l0 2.0l-1.65625 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m338.45297 101.63078l-217.54332 -0.09449005" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m332.45297 101.62818l-211.54332 -0.09188843" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m332.45224 103.27991l4.5388184 -1.6497574l-4.537384 -1.6537094z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m193.7088 209.21298l0 0c0 -3.7747498 3.0600433 -6.834778 6.834793 -6.834778l27.338303 0l0 0c1.8126984 0 3.5511475 0.7200928 4.8329163 2.0018616c1.2817688 1.2817688 2.0018616 3.020218 2.0018616 4.8329163l0 78.83438c0 3.7747498 -3.060028 6.834778 -6.834778 6.834778l-27.338303 0c-3.7747498 0 -6.834793 -3.060028 -6.834793 -6.834778z" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m193.7088 209.21298l0 0c0 -3.7747498 3.0600433 -6.834778 6.834793 -6.834778l27.338303 0l0 0c1.8126984 0 3.5511475 0.7200928 4.8329163 2.0018616c1.2817688 1.2817688 2.0018616 3.020218 2.0018616 4.8329163l0 78.83438c0 3.7747498 -3.060028 6.834778 -6.834778 6.834778l-27.338303 0c-3.7747498 0 -6.834793 -3.060028 -6.834793 -6.834778z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m243.38414 192.38083l102.74016 0l0 23.496063l-102.74016 0z" fill-rule="evenodd"></path><path fill="#000000" d="m252.38414 213.04144l2.484375 -8.875l0.84375 0l-2.484375 8.875l-0.84375 0zm3.7089996 -2.0l1.03125 -0.15625q0.09375 0.625 0.484375 0.953125q0.40625 0.328125 1.140625 0.328125q0.71875 0 1.0625 -0.28125q0.359375 -0.296875 0.359375 -0.703125q0 -0.359375 -0.3125 -0.5625q-0.21875 -0.140625 -1.078125 -0.359375q-1.15625 -0.296875 -1.609375 -0.5q-0.4375 -0.21875 -0.671875 -0.59375q-0.234375 -0.375 -0.234375 -0.84375q0 -0.40625 0.1875 -0.765625q0.1875 -0.359375 0.515625 -0.59375q0.25 -0.171875 0.671875 -0.296875q0.421875 -0.125 0.921875 -0.125q0.71875 0 1.265625 0.21875q0.5625 0.203125 0.828125 0.5625q0.265625 0.359375 0.359375 0.953125l-1.03125 0.140625q-0.0625 -0.46875 -0.40625 -0.734375q-0.328125 -0.28125 -0.953125 -0.28125q-0.71875 0 -1.03125 0.25q-0.3125 0.234375 -0.3125 0.5625q0 0.203125 0.125 0.359375q0.140625 0.171875 0.40625 0.28125q0.15625 0.0625 0.9375 0.265625q1.125 0.3125 1.5625 0.5q0.4375 0.1875 0.6875 0.546875q0.25 0.359375 0.25 0.90625q0 0.53125 -0.3125 1.0q-0.296875 0.453125 -0.875 0.71875q-0.578125 0.25 -1.3125 0.25q-1.21875 0 -1.859375 -0.5q-0.625 -0.515625 -0.796875 -1.5zm10.5 1.859375l0 -0.921875q-0.734375 1.0625 -1.984375 1.0625q-0.546875 0 -1.03125 -0.203125q-0.46875 -0.21875 -0.703125 -0.53125q-0.234375 -0.328125 -0.328125 -0.796875q-0.0625 -0.296875 -0.0625 -0.984375l0 -3.84375l1.0625 0l0 3.453125q0 0.8125 0.0625 1.109375q0.09375 0.40625 0.40625 0.65625q0.328125 0.234375 0.8125 0.234375q0.46875 0 0.875 -0.234375q0.421875 -0.25 0.59375 -0.671875q0.1875 -0.421875 0.1875 -1.21875l0 -3.328125l1.046875 0l0 6.21875l-0.9375 0zm6.658203 -2.28125l1.03125 0.140625q-0.171875 1.0625 -0.875 1.671875q-0.703125 0.609375 -1.71875 0.609375q-1.28125 0 -2.0625 -0.828125q-0.765625 -0.84375 -0.765625 -2.40625q0 -1.0 0.328125 -1.75q0.34375 -0.765625 1.015625 -1.140625q0.6875 -0.375 1.5 -0.375q1.0 0 1.640625 0.515625q0.65625 0.5 0.84375 1.453125l-1.03125 0.15625q-0.140625 -0.625 -0.515625 -0.9375q-0.375 -0.328125 -0.90625 -0.328125q-0.796875 0 -1.296875 0.578125q-0.5 0.5625 -0.5 1.796875q0 1.265625 0.484375 1.828125q0.484375 0.5625 1.25 0.5625q0.625 0 1.03125 -0.375q0.421875 -0.375 0.546875 -1.171875zm6.0 0l1.03125 0.140625q-0.171875 1.0625 -0.875 1.671875q-0.703125 0.609375 -1.71875 0.609375q-1.28125 0 -2.0625 -0.828125q-0.765625 -0.84375 -0.765625 -2.40625q0 -1.0 0.328125 -1.75q0.34375 -0.765625 1.015625 -1.140625q0.6875 -0.375 1.5 -0.375q1.0 0 1.640625 0.515625q0.65625 0.5 0.84375 1.453125l-1.03125 0.15625q-0.140625 -0.625 -0.515625 -0.9375q-0.375 -0.328125 -0.90625 -0.328125q-0.796875 0 -1.296875 0.578125q-0.5 0.5625 -0.5 1.796875q0 1.265625 0.484375 1.828125q0.484375 0.5625 1.25 0.5625q0.625 0 1.03125 -0.375q0.421875 -0.375 0.546875 -1.171875zm6.1875 0.28125l1.09375 0.125q-0.25 0.953125 -0.953125 1.484375q-0.703125 0.53125 -1.78125 0.53125q-1.359375 0 -2.171875 -0.84375q-0.796875 -0.84375 -0.796875 -2.359375q0 -1.5625 0.8125 -2.421875q0.8125 -0.875 2.09375 -0.875q1.25 0 2.03125 0.84375q0.796875 0.84375 0.796875 2.390625q0 0.09375 0 0.28125l-4.640625 0q0.0625 1.03125 0.578125 1.578125q0.515625 0.53125 1.296875 0.53125q0.578125 0 0.984375 -0.296875q0.421875 -0.3125 0.65625 -0.96875zm-3.453125 -1.703125l3.46875 0q-0.0625 -0.796875 -0.390625 -1.1875q-0.515625 -0.609375 -1.3125 -0.609375q-0.734375 0 -1.234375 0.484375q-0.484375 0.484375 -0.53125 1.3125zm5.455078 1.84375l1.03125 -0.15625q0.09375 0.625 0.484375 0.953125q0.40625 0.328125 1.140625 0.328125q0.71875 0 1.0625 -0.28125q0.359375 -0.296875 0.359375 -0.703125q0 -0.359375 -0.3125 -0.5625q-0.21875 -0.140625 -1.078125 -0.359375q-1.15625 -0.296875 -1.609375 -0.5q-0.4375 -0.21875 -0.671875 -0.59375q-0.234375 -0.375 -0.234375 -0.84375q0 -0.40625 0.1875 -0.765625q0.1875 -0.359375 0.515625 -0.59375q0.25 -0.171875 0.671875 -0.296875q0.421875 -0.125 0.921875 -0.125q0.71875 0 1.265625 0.21875q0.5625 0.203125 0.828125 0.5625q0.265625 0.359375 0.359375 0.953125l-1.03125 0.140625q-0.0625 -0.46875 -0.40625 -0.734375q-0.328125 -0.28125 -0.953125 -0.28125q-0.71875 0 -1.03125 0.25q-0.3125 0.234375 -0.3125 0.5625q0 0.203125 0.125 0.359375q0.140625 0.171875 0.40625 0.28125q0.15625 0.0625 0.9375 0.265625q1.125 0.3125 1.5625 0.5q0.4375 0.1875 0.6875 0.546875q0.25 0.359375 0.25 0.90625q0 0.53125 -0.3125 1.0q-0.296875 0.453125 -0.875 0.71875q-0.578125 0.25 -1.3125 0.25q-1.21875 0 -1.859375 -0.5q-0.625 -0.515625 -0.796875 -1.5zm6.0 0l1.03125 -0.15625q0.09375 0.625 0.484375 0.953125q0.40625 0.328125 1.140625 0.328125q0.71875 0 1.0625 -0.28125q0.359375 -0.296875 0.359375 -0.703125q0 -0.359375 -0.3125 -0.5625q-0.21875 -0.140625 -1.078125 -0.359375q-1.15625 -0.296875 -1.609375 -0.5q-0.4375 -0.21875 -0.671875 -0.59375q-0.234375 -0.375 -0.234375 -0.84375q0 -0.40625 0.1875 -0.765625q0.1875 -0.359375 0.515625 -0.59375q0.25 -0.171875 0.671875 -0.296875q0.421875 -0.125 0.921875 -0.125q0.71875 0 1.265625 0.21875q0.5625 0.203125 0.828125 0.5625q0.265625 0.359375 0.359375 0.953125l-1.03125 0.140625q-0.0625 -0.46875 -0.40625 -0.734375q-0.328125 -0.28125 -0.953125 -0.28125q-0.71875 0 -1.03125 0.25q-0.3125 0.234375 -0.3125 0.5625q0 0.203125 0.125 0.359375q0.140625 0.171875 0.40625 0.28125q0.15625 0.0625 0.9375 0.265625q1.125 0.3125 1.5625 0.5q0.4375 0.1875 0.6875 0.546875q0.25 0.359375 0.25 0.90625q0 0.53125 -0.3125 1.0q-0.296875 0.453125 -0.875 0.71875q-0.578125 0.25 -1.3125 0.25q-1.21875 0 -1.859375 -0.5q-0.625 -0.515625 -0.796875 -1.5zm8.4375 4.390625q-0.875 -1.109375 -1.484375 -2.578125q-0.59375 -1.484375 -0.59375 -3.0625q0 -1.390625 0.4375 -2.671875q0.53125 -1.484375 1.640625 -2.953125l0.75 0q-0.703125 1.21875 -0.9375 1.734375q-0.359375 0.8125 -0.5625 1.6875q-0.25 1.09375 -0.25 2.203125q0 2.828125 1.75 5.640625l-0.75 0zm4.2773438 -3.46875l0.15625 0.921875q-0.453125 0.09375 -0.796875 0.09375q-0.578125 0 -0.890625 -0.171875q-0.3125 -0.1875 -0.453125 -0.484375q-0.125 -0.296875 -0.125 -1.25l0 -3.578125l-0.765625 0l0 -0.8125l0.765625 0l0 -1.546875l1.046875 -0.625l0 2.171875l1.0625 0l0 0.8125l-1.0625 0l0 3.640625q0 0.453125 0.046875 0.578125q0.0625 0.125 0.1875 0.203125q0.125 0.078125 0.359375 0.078125q0.1875 0 0.46875 -0.03125zm1.0214844 0.9375l0 -6.21875l0.953125 0l0 0.9375q0.359375 -0.65625 0.65625 -0.859375q0.3125 -0.21875 0.6875 -0.21875q0.53125 0 1.078125 0.328125l-0.359375 0.984375q-0.390625 -0.234375 -0.765625 -0.234375q-0.359375 0 -0.640625 0.21875q-0.265625 0.203125 -0.375 0.578125q-0.1875 0.5625 -0.1875 1.21875l0 3.265625l-1.046875 0zm8.089844 0l0 -0.921875q-0.734375 1.0625 -1.984375 1.0625q-0.546875 0 -1.03125 -0.203125q-0.46875 -0.21875 -0.703125 -0.53125q-0.234375 -0.328125 -0.328125 -0.796875q-0.0625 -0.296875 -0.0625 -0.984375l0 -3.84375l1.0625 0l0 3.453125q0 0.8125 0.0625 1.109375q0.09375 0.40625 0.40625 0.65625q0.328125 0.234375 0.8125 0.234375q0.46875 0 0.875 -0.234375q0.421875 -0.25 0.59375 -0.671875q0.1875 -0.421875 0.1875 -1.21875l0 -3.328125l1.046875 0l0 6.21875l-0.9375 0zm6.845703 -2.0l1.09375 0.125q-0.25 0.953125 -0.953125 1.484375q-0.703125 0.53125 -1.78125 0.53125q-1.359375 0 -2.171875 -0.84375q-0.796875 -0.84375 -0.796875 -2.359375q0 -1.5625 0.8125 -2.421875q0.8125 -0.875 2.09375 -0.875q1.25 0 2.03125 0.84375q0.796875 0.84375 0.796875 2.390625q0 0.09375 0 0.28125l-4.640625 0q0.0625 1.03125 0.578125 1.578125q0.515625 0.53125 1.296875 0.53125q0.578125 0 0.984375 -0.296875q0.421875 -0.3125 0.65625 -0.96875zm-3.453125 -1.703125l3.46875 0q-0.0625 -0.796875 -0.390625 -1.1875q-0.515625 -0.609375 -1.3125 -0.609375q-0.734375 0 -1.234375 0.484375q-0.484375 0.484375 -0.53125 1.3125zm6.564453 6.234375l-0.75 0q1.75 -2.8125 1.75 -5.640625q0 -1.09375 -0.25 -2.1875q-0.203125 -0.875 -0.5625 -1.6875q-0.234375 -0.515625 -0.9375 -1.75l0.75 0q1.09375 1.46875 1.625 2.953125q0.453125 1.28125 0.453125 2.671875q0 1.578125 -0.609375 3.0625q-0.609375 1.46875 -1.46875 2.578125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m338.557 220.3791l-103.55905 0" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m338.557 220.3791l-97.55905 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m240.99796 218.72737l-4.538101 1.6517334l4.538101 1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m264.05698 221.6883l64.28348 0l0 23.496063l-64.28348 0z" fill-rule="evenodd"></path><path fill="#000000" d="m279.5726 242.2083l-1.640625 0l0 -3.171875q0 -1.015625 -0.109375 -1.3125q-0.09375 -0.296875 -0.34375 -0.453125q-0.234375 -0.171875 -0.5625 -0.171875q-0.4375 0 -0.78125 0.234375q-0.328125 0.234375 -0.453125 0.625q-0.125 0.390625 -0.125 1.4375l0 2.8125l-1.65625 0l0 -6.21875l1.53125 0l0 0.90625q0.8125 -1.046875 2.0625 -1.046875q0.53125 0 0.984375 0.203125q0.453125 0.1875 0.6875 0.5q0.234375 0.296875 0.3125 0.6875q0.09375 0.375 0.09375 1.09375l0 3.875zm5.283203 -1.984375l1.640625 0.28125q-0.3125 0.90625 -1.0 1.375q-0.6875 0.46875 -1.703125 0.46875q-1.625 0 -2.40625 -1.0625q-0.625 -0.84375 -0.625 -2.140625q0 -1.546875 0.8125 -2.421875q0.8125 -0.875 2.046875 -0.875q1.390625 0 2.1875 0.921875q0.8125 0.90625 0.765625 2.796875l-4.125 0q0.03125 0.734375 0.40625 1.140625q0.375 0.40625 0.953125 0.40625q0.375 0 0.640625 -0.203125q0.265625 -0.21875 0.40625 -0.6875zm0.09375 -1.65625q-0.015625 -0.71875 -0.375 -1.09375q-0.34375 -0.375 -0.859375 -0.375q-0.53125 0 -0.890625 0.390625q-0.34375 0.40625 -0.34375 1.078125l2.46875 0zm2.1894531 3.640625l2.234375 -3.203125l-2.140625 -3.015625l2.0 0l1.109375 1.703125l1.15625 -1.703125l1.9375 0l-2.109375 2.9375l2.296875 3.28125l-2.015625 0l-1.265625 -1.921875l-1.28125 1.921875l-1.921875 0zm10.314453 -6.21875l0 1.3125l-1.125 0l0 2.5q0 0.765625 0.03125 0.890625q0.03125 0.125 0.140625 0.21875q0.125 0.078125 0.28125 0.078125q0.234375 0 0.65625 -0.171875l0.140625 1.28125q-0.5625 0.25 -1.296875 0.25q-0.4375 0 -0.796875 -0.140625q-0.359375 -0.15625 -0.53125 -0.390625q-0.15625 -0.25 -0.234375 -0.640625q-0.046875 -0.296875 -0.046875 -1.171875l0 -2.703125l-0.75 0l0 -1.3125l0.75 0l0 -1.234375l1.65625 -0.96875l0 2.203125l1.125 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m337.9245 249.08253l-103.30708 0" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m331.9245 249.08253l-97.30708 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m331.9245 250.73427l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m126.02627 191.99237l104.88189 0l0 23.496063l-104.88189 0z" fill-rule="evenodd"></path><path fill="#000000" d="m135.30751 210.73112l1.65625 -0.25q0.109375 0.484375 0.421875 0.734375q0.328125 0.25 0.90625 0.25q0.640625 0 0.953125 -0.234375q0.21875 -0.171875 0.21875 -0.4375q0 -0.1875 -0.109375 -0.3125q-0.125 -0.125 -0.546875 -0.21875q-2.0 -0.4375 -2.53125 -0.796875q-0.734375 -0.515625 -0.734375 -1.40625q0 -0.8125 0.625 -1.359375q0.640625 -0.546875 1.984375 -0.546875q1.28125 0 1.90625 0.421875q0.625 0.40625 0.859375 1.21875l-1.5625 0.28125q-0.09375 -0.359375 -0.375 -0.546875q-0.28125 -0.203125 -0.796875 -0.203125q-0.640625 0 -0.921875 0.1875q-0.1875 0.125 -0.1875 0.328125q0 0.1875 0.15625 0.3125q0.21875 0.15625 1.53125 0.453125q1.328125 0.296875 1.84375 0.734375q0.515625 0.4375 0.515625 1.21875q0 0.859375 -0.71875 1.484375q-0.703125 0.609375 -2.109375 0.609375q-1.265625 0 -2.015625 -0.515625q-0.734375 -0.515625 -0.96875 -1.40625zm10.111328 -4.4375l0 1.3125l-1.125 0l0 2.5q0 0.765625 0.03125 0.890625q0.03125 0.125 0.140625 0.21875q0.125 0.078125 0.28125 0.078125q0.234375 0 0.65625 -0.171875l0.140625 1.28125q-0.5625 0.25 -1.296875 0.25q-0.4375 0 -0.796875 -0.140625q-0.359375 -0.15625 -0.53125 -0.390625q-0.15625 -0.25 -0.234375 -0.640625q-0.046875 -0.296875 -0.046875 -1.171875l0 -2.703125l-0.75 0l0 -1.3125l0.75 0l0 -1.234375l1.65625 -0.96875l0 2.203125l1.125 0zm0.76171875 3.015625q0 -0.8125 0.40625 -1.578125q0.40625 -0.78125 1.140625 -1.171875q0.734375 -0.40625 1.65625 -0.40625q1.40625 0 2.3125 0.921875q0.90625 0.90625 0.90625 2.3125q0 1.40625 -0.921875 2.34375q-0.90625 0.921875 -2.28125 0.921875q-0.859375 0 -1.640625 -0.390625q-0.765625 -0.390625 -1.171875 -1.125q-0.40625 -0.75 -0.40625 -1.828125zm1.6875 0.09375q0 0.921875 0.4375 1.421875q0.4375 0.484375 1.078125 0.484375q0.65625 0 1.078125 -0.484375q0.4375 -0.5 0.4375 -1.4375q0 -0.90625 -0.4375 -1.390625q-0.421875 -0.5 -1.078125 -0.5q-0.640625 0 -1.078125 0.5q-0.4375 0.484375 -0.4375 1.40625zm5.970703 -3.109375l1.53125 0l0 0.90625q0.3125 -0.46875 0.8125 -0.75q0.515625 -0.296875 1.140625 -0.296875q1.078125 0 1.828125 0.84375q0.765625 0.84375 0.765625 2.375q0 1.546875 -0.765625 2.421875q-0.765625 0.859375 -1.84375 0.859375q-0.515625 0 -0.9375 -0.203125q-0.421875 -0.203125 -0.875 -0.703125l0 3.140625l-1.65625 0l0 -8.59375zm1.625 3.0q0 1.046875 0.421875 1.546875q0.421875 0.5 1.015625 0.5q0.578125 0 0.953125 -0.453125q0.375 -0.46875 0.375 -1.515625q0 -0.96875 -0.390625 -1.4375q-0.390625 -0.484375 -0.96875 -0.484375q-0.609375 0 -1.015625 0.46875q-0.390625 0.46875 -0.390625 1.375z" fill-rule="nonzero"></path><path fill="#000000" d="m160.35634 212.653l2.484375 -8.875l0.84375 0l-2.484375 8.875l-0.84375 0zm3.7089844 -2.0l1.03125 -0.15625q0.09375 0.625 0.484375 0.953125q0.40625 0.328125 1.140625 0.328125q0.71875 0 1.0625 -0.28125q0.359375 -0.296875 0.359375 -0.703125q0 -0.359375 -0.3125 -0.5625q-0.21875 -0.140625 -1.078125 -0.359375q-1.15625 -0.296875 -1.609375 -0.5q-0.4375 -0.21875 -0.671875 -0.59375q-0.234375 -0.375 -0.234375 -0.84375q0 -0.40625 0.1875 -0.765625q0.1875 -0.359375 0.515625 -0.59375q0.25 -0.171875 0.671875 -0.296875q0.421875 -0.125 0.921875 -0.125q0.71875 0 1.265625 0.21875q0.5625 0.203125 0.828125 0.5625q0.265625 0.359375 0.359375 0.953125l-1.03125 0.140625q-0.0625 -0.46875 -0.40625 -0.734375q-0.328125 -0.28125 -0.953125 -0.28125q-0.71875 0 -1.03125 0.25q-0.3125 0.234375 -0.3125 0.5625q0 0.203125 0.125 0.359375q0.140625 0.171875 0.40625 0.28125q0.15625 0.0625 0.9375 0.265625q1.125 0.3125 1.5625 0.5q0.4375 0.1875 0.6875 0.546875q0.25 0.359375 0.25 0.90625q0 0.53125 -0.3125 1.0q-0.296875 0.453125 -0.875 0.71875q-0.578125 0.25 -1.3125 0.25q-1.21875 0 -1.859375 -0.5q-0.625 -0.515625 -0.796875 -1.5zm8.71875 0.921875l0.15625 0.921875q-0.453125 0.09375 -0.796875 0.09375q-0.578125 0 -0.890625 -0.171875q-0.3125 -0.1875 -0.453125 -0.484375q-0.125 -0.296875 -0.125 -1.25l0 -3.578125l-0.765625 0l0 -0.8125l0.765625 0l0 -1.546875l1.046875 -0.625l0 2.171875l1.0625 0l0 0.8125l-1.0625 0l0 3.640625q0 0.453125 0.046875 0.578125q0.0625 0.125 0.1875 0.203125q0.125 0.078125 0.359375 0.078125q0.1875 0 0.46875 -0.03125zm0.6464844 -2.171875q0 -1.734375 0.953125 -2.5625q0.796875 -0.6875 1.953125 -0.6875q1.28125 0 2.09375 0.84375q0.828125 0.828125 0.828125 2.3125q0 1.203125 -0.359375 1.890625q-0.359375 0.6875 -1.0625 1.078125q-0.6875 0.375 -1.5 0.375q-1.296875 0 -2.109375 -0.828125q-0.796875 -0.84375 -0.796875 -2.421875zm1.078125 0q0 1.1875 0.515625 1.78125q0.53125 0.59375 1.3125 0.59375q0.796875 0 1.3125 -0.59375q0.515625 -0.59375 0.515625 -1.8125q0 -1.15625 -0.53125 -1.75q-0.515625 -0.59375 -1.296875 -0.59375q-0.78125 0 -1.3125 0.59375q-0.515625 0.578125 -0.515625 1.78125zm5.986328 5.5l0 -8.609375l0.953125 0l0 0.796875q0.34375 -0.46875 0.765625 -0.703125q0.4375 -0.234375 1.046875 -0.234375q0.796875 0 1.40625 0.40625q0.609375 0.40625 0.90625 1.15625q0.3125 0.75 0.3125 1.640625q0 0.953125 -0.34375 1.71875q-0.328125 0.765625 -0.984375 1.171875q-0.65625 0.40625 -1.375 0.40625q-0.53125 0 -0.953125 -0.21875q-0.421875 -0.234375 -0.6875 -0.5625l0 3.03125l-1.046875 0zm0.953125 -5.46875q0 1.203125 0.484375 1.78125q0.484375 0.5625 1.171875 0.5625q0.703125 0 1.203125 -0.59375q0.5 -0.59375 0.5 -1.84375q0 -1.1875 -0.484375 -1.765625q-0.484375 -0.59375 -1.171875 -0.59375q-0.671875 0 -1.1875 0.625q-0.515625 0.625 -0.515625 1.828125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m193.07877 220.77321l-71.93701 0" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m193.07877 220.77321l-65.93701 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m127.141754 219.12148l-4.538101 1.6517334l4.538101 1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m589.5729 169.03986l0 0c0 -6.488266 5.1469727 -11.748032 11.496033 -11.748032l0 0c3.0489502 0 5.9730225 1.2377319 8.128967 3.440918c2.1559448 2.203186 3.3671265 5.191345 3.3671265 8.307114l0 0c0 6.4882507 -5.1469727 11.748032 -11.496094 11.748032l0 0c-6.34906 0 -11.496033 -5.259781 -11.496033 -11.748032z" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="2.0" stroke-linejoin="round" stroke-linecap="butt" d="m589.5729 169.03986l0 0c0 -6.488266 5.1469727 -11.748032 11.496033 -11.748032l0 0c3.0489502 0 5.9730225 1.2377319 8.128967 3.440918c2.1559448 2.203186 3.3671265 5.191345 3.3671265 8.307114l0 0c0 6.4882507 -5.1469727 11.748032 -11.496094 11.748032l0 0c-6.34906 0 -11.496033 -5.259781 -11.496033 -11.748032z" fill-rule="evenodd"></path><path fill="#000000" d="m594.78467 169.0233l0 0c0 -3.6007233 2.9189453 -6.519684 6.5196533 -6.519684l0 0c1.729126 0 3.3874512 0.6869049 4.6101074 1.9095764c1.2227173 1.2226715 1.9096069 2.8809814 1.9096069 4.6101074l0 0c0 3.6007233 -2.9189453 6.519699 -6.5197144 6.519699l0 0c-3.600708 0 -6.5196533 -2.9189758 -6.5196533 -6.519699z" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="2.0" stroke-linejoin="round" stroke-linecap="butt" d="m594.78467 169.0233l0 0c0 -3.6007233 2.9189453 -6.519684 6.5196533 -6.519684l0 0c1.729126 0 3.3874512 0.6869049 4.6101074 1.9095764c1.2227173 1.2226715 1.9096069 2.8809814 1.9096069 4.6101074l0 0c0 3.6007233 -2.9189453 6.519699 -6.5197144 6.519699l0 0c-3.600708 0 -6.5196533 -2.9189758 -6.5196533 -6.519699z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m520.7664 169.03937l68.81891 0" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m520.7664 169.03937l62.81891 0" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m583.5853 170.6911l4.538086 -1.6517334l-4.538086 -1.6517334z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m521.40686 141.03394l77.82678 0l0 36.0l-77.82678 0z" fill-rule="evenodd"></path><path fill="#000000" d="m534.8756 159.56956l1.640625 0.28125q-0.3125 0.90625 -1.0 1.375q-0.6875 0.46875 -1.703125 0.46875q-1.625 0 -2.40625 -1.0625q-0.625 -0.84375 -0.625 -2.140625q0 -1.546875 0.8125 -2.421875q0.8125 -0.875 2.046875 -0.875q1.390625 0 2.1875 0.921875q0.8125 0.90625 0.765625 2.796875l-4.125 0q0.03125 0.734375 0.40625 1.140625q0.375 0.40625 0.953125 0.40625q0.375 0 0.640625 -0.203125q0.265625 -0.21875 0.40625 -0.6875zm0.09375 -1.65625q-0.015625 -0.71875 -0.375 -1.09375q-0.34375 -0.375 -0.859375 -0.375q-0.53125 0 -0.890625 0.390625q-0.34375 0.40625 -0.34375 1.078125l2.46875 0zm2.1894531 3.640625l2.234375 -3.203125l-2.140625 -3.015625l2.0 0l1.109375 1.703125l1.15625 -1.703125l1.9375 0l-2.109375 2.9375l2.296875 3.28125l-2.015625 0l-1.265625 -1.921875l-1.28125 1.921875l-1.921875 0zm7.455078 -7.0625l0 -1.53125l1.65625 0l0 1.53125l-1.65625 0zm0 7.0625l0 -6.21875l1.65625 0l0 6.21875l-1.65625 0zm6.1933594 -6.21875l0 1.3125l-1.125 0l0 2.5q0 0.765625 0.03125 0.890625q0.03125 0.125 0.140625 0.21875q0.125 0.078125 0.28125 0.078125q0.234375 0 0.65625 -0.171875l0.140625 1.28125q-0.5625 0.25 -1.296875 0.25q-0.4375 0 -0.796875 -0.140625q-0.359375 -0.15625 -0.53125 -0.390625q-0.15625 -0.25 -0.234375 -0.640625q-0.046875 -0.296875 -0.046875 -1.171875l0 -2.703125l-0.75 0l0 -1.3125l0.75 0l0 -1.234375l1.65625 -0.96875l0 2.203125l1.125 0z" fill-rule="nonzero"></path><path fill="#000000" d="m551.0846 161.69456l2.484375 -8.875l0.84375 0l-2.484375 8.875l-0.84375 0zm8.162109 -0.140625l0 -0.78125q-0.59375 0.921875 -1.734375 0.921875q-0.75 0 -1.375 -0.40625q-0.625 -0.421875 -0.96875 -1.15625q-0.34375 -0.734375 -0.34375 -1.6875q0 -0.921875 0.3125 -1.6875q0.3125 -0.765625 0.9375 -1.15625q0.625 -0.40625 1.390625 -0.40625q0.5625 0 1.0 0.234375q0.4375 0.234375 0.71875 0.609375l0 -3.078125l1.046875 0l0 8.59375l-0.984375 0zm-3.328125 -3.109375q0 1.203125 0.5 1.796875q0.5 0.578125 1.1875 0.578125q0.6875 0 1.171875 -0.5625q0.484375 -0.5625 0.484375 -1.71875q0 -1.28125 -0.5 -1.875q-0.484375 -0.59375 -1.203125 -0.59375q-0.703125 0 -1.171875 0.578125q-0.46875 0.5625 -0.46875 1.796875zm5.580078 0q0 -1.734375 0.953125 -2.5625q0.796875 -0.6875 1.953125 -0.6875q1.28125 0 2.09375 0.84375q0.828125 0.828125 0.828125 2.3125q0 1.203125 -0.359375 1.890625q-0.359375 0.6875 -1.0625 1.078125q-0.6875 0.375 -1.5 0.375q-1.296875 0 -2.109375 -0.828125q-0.796875 -0.84375 -0.796875 -2.421875zm1.078125 0q0 1.1875 0.515625 1.78125q0.53125 0.59375 1.3125 0.59375q0.796875 0 1.3125 -0.59375q0.515625 -0.59375 0.515625 -1.8125q0 -1.15625 -0.53125 -1.75q-0.515625 -0.59375 -1.296875 -0.59375q-0.78125 0 -1.3125 0.59375q-0.515625 0.578125 -0.515625 1.78125zm7.126953 3.109375l-1.90625 -6.21875l1.09375 0l0.984375 3.59375l0.375 1.328125q0.03125 -0.09375 0.328125 -1.28125l0.984375 -3.640625l1.078125 0l0.9375 3.609375l0.3125 1.1875l0.359375 -1.203125l1.0625 -3.59375l1.03125 0l-1.953125 6.21875l-1.09375 0l-0.984375 -3.734375l-0.25 -1.046875l-1.25 4.78125l-1.109375 0zm7.5253906 0l0 -6.21875l0.9375 0l0 0.875q0.6875 -1.015625 1.984375 -1.015625q0.5625 0 1.03125 0.203125q0.484375 0.203125 0.71875 0.53125q0.234375 0.328125 0.328125 0.765625q0.046875 0.296875 0.046875 1.03125l0 3.828125l-1.046875 0l0 -3.78125q0 -0.65625 -0.125 -0.96875q-0.125 -0.3125 -0.4375 -0.5q-0.3125 -0.203125 -0.734375 -0.203125q-0.671875 0 -1.171875 0.4375q-0.484375 0.421875 -0.484375 1.609375l0 3.40625l-1.046875 0zm6.251953 -7.015625l0.265625 -0.828125q0.9375 0.328125 1.359375 0.5625q-0.109375 -1.0625 -0.125 -1.453125l0.859375 0q-0.015625 0.578125 -0.140625 1.453125q0.609375 -0.3125 1.390625 -0.5625l0.265625 0.828125q-0.75 0.25 -1.453125 0.328125q0.34375 0.3125 1.0 1.109375l-0.703125 0.5q-0.34375 -0.46875 -0.796875 -1.265625q-0.4375 0.828125 -0.765625 1.265625l-0.6875 -0.5q0.671875 -0.84375 0.96875 -1.109375q-0.75 -0.140625 -1.4375 -0.328125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m100.41991 30.740404l-0.12598419 44.34646" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m100.41991 30.740404l-0.108932495 38.34648" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m98.65924 69.08219l1.6388321 4.5427704l1.6646194 -4.533386z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m96.91599 32.484493l86.015755 0l0 27.496063l-86.015755 0z" fill-rule="evenodd"></path><path fill="#000000" d="m108.00974 48.67637l-1.5 -0.265625q0.25 -0.90625 0.859375 -1.328125q0.625 -0.4375 1.84375 -0.4375q1.09375 0 1.625 0.265625q0.546875 0.25 0.765625 0.65625q0.21875 0.390625 0.21875 1.46875l-0.015625 1.921875q0 0.828125 0.078125 1.21875q0.078125 0.375 0.296875 0.828125l-1.625 0q-0.0625 -0.171875 -0.15625 -0.484375q-0.046875 -0.15625 -0.0625 -0.203125q-0.421875 0.421875 -0.90625 0.625q-0.46875 0.203125 -1.015625 0.203125q-0.96875 0 -1.53125 -0.515625q-0.546875 -0.53125 -0.546875 -1.328125q0 -0.53125 0.25 -0.9375q0.265625 -0.40625 0.71875 -0.625q0.453125 -0.234375 1.3125 -0.390625q1.140625 -0.21875 1.59375 -0.40625l0 -0.15625q0 -0.484375 -0.234375 -0.6875q-0.234375 -0.203125 -0.890625 -0.203125q-0.4375 0 -0.6875 0.171875q-0.234375 0.171875 -0.390625 0.609375zm2.203125 1.34375q-0.3125 0.09375 -1.0 0.25q-0.6875 0.140625 -0.90625 0.28125q-0.3125 0.234375 -0.3125 0.578125q0 0.34375 0.25 0.609375q0.265625 0.25 0.65625 0.25q0.453125 0 0.859375 -0.296875q0.296875 -0.21875 0.390625 -0.546875q0.0625 -0.203125 0.0625 -0.796875l0 -0.328125zm3.1738281 2.984375l0 -8.59375l1.640625 0l0 3.09375q0.765625 -0.859375 1.8125 -0.859375q1.125 0 1.875 0.828125q0.75 0.8125 0.75 2.359375q0 1.59375 -0.765625 2.453125q-0.765625 0.859375 -1.84375 0.859375q-0.53125 0 -1.046875 -0.265625q-0.515625 -0.265625 -0.890625 -0.796875l0 0.921875l-1.53125 0zm1.625 -3.25q0 0.96875 0.3125 1.4375q0.421875 0.65625 1.140625 0.65625q0.53125 0 0.921875 -0.46875q0.390625 -0.46875 0.390625 -1.46875q0 -1.0625 -0.390625 -1.53125q-0.390625 -0.484375 -1.0 -0.484375q-0.578125 0 -0.984375 0.46875q-0.390625 0.453125 -0.390625 1.390625zm5.392578 0.046875q0 -0.8125 0.40625 -1.578125q0.40625 -0.78125 1.140625 -1.171875q0.734375 -0.40625 1.65625 -0.40625q1.40625 0 2.3125 0.921875q0.90625 0.90625 0.90625 2.3125q0 1.40625 -0.921875 2.34375q-0.90625 0.921875 -2.28125 0.921875q-0.859375 0 -1.640625 -0.390625q-0.765625 -0.390625 -1.171875 -1.125q-0.40625 -0.75 -0.40625 -1.828125zm1.6875 0.09375q0 0.921875 0.4375 1.421875q0.4375 0.484375 1.078125 0.484375q0.65625 0 1.078125 -0.484375q0.4375 -0.5 0.4375 -1.4375q0 -0.90625 -0.4375 -1.390625q-0.421875 -0.5 -1.078125 -0.5q-0.640625 0 -1.078125 0.5q-0.4375 0.484375 -0.4375 1.40625zm7.5957108 3.109375l-1.640625 0l0 -6.21875l1.53125 0l0 0.875q0.390625 -0.625 0.703125 -0.8125q0.3125 -0.203125 0.703125 -0.203125q0.5625 0 1.09375 0.3125l-0.515625 1.421875q-0.421875 -0.265625 -0.765625 -0.265625q-0.359375 0 -0.59375 0.203125q-0.234375 0.1875 -0.375 0.6875q-0.140625 0.484375 -0.140625 2.078125l0 1.921875zm5.951172 -6.21875l0 1.3125l-1.125 0l0 2.5q0 0.765625 0.03125 0.890625q0.03125 0.125 0.140625 0.21875q0.125 0.078125 0.28125 0.078125q0.234375 0 0.65625 -0.171875l0.140625 1.28125q-0.5625 0.25 -1.296875 0.25q-0.4375 0 -0.796875 -0.140625q-0.359375 -0.15625 -0.53125 -0.390625q-0.15625 -0.25 -0.234375 -0.640625q-0.046875 -0.296875 -0.046875 -1.171875l0 -2.703125l-0.75 0l0 -1.3125l0.75 0l0 -1.234375l1.65625 -0.96875l0 2.203125l1.125 0z" fill-rule="nonzero"></path><path fill="#000000" d="m135.916 53.14512l2.484375 -8.875l0.84375 0l-2.484375 8.875l-0.84375 0zm8.193359 -0.90625q-0.59375 0.5 -1.140625 0.703125q-0.53125 0.203125 -1.15625 0.203125q-1.03125 0 -1.578125 -0.5q-0.546875 -0.5 -0.546875 -1.28125q0 -0.453125 0.203125 -0.828125q0.203125 -0.390625 0.546875 -0.609375q0.34375 -0.234375 0.765625 -0.34375q0.296875 -0.09375 0.9375 -0.171875q1.265625 -0.140625 1.875 -0.359375q0 -0.21875 0 -0.265625q0 -0.65625 -0.296875 -0.921875q-0.40625 -0.34375 -1.203125 -0.34375q-0.734375 0 -1.09375 0.265625q-0.359375 0.25 -0.53125 0.90625l-1.03125 -0.140625q0.140625 -0.65625 0.46875 -1.0625q0.328125 -0.40625 0.9375 -0.625q0.609375 -0.21875 1.40625 -0.21875q0.796875 0 1.296875 0.1875q0.5 0.1875 0.734375 0.46875q0.234375 0.28125 0.328125 0.71875q0.046875 0.265625 0.046875 0.96875l0 1.40625q0 1.46875 0.0625 1.859375q0.078125 0.390625 0.28125 0.75l-1.109375 0q-0.15625 -0.328125 -0.203125 -0.765625zm-0.09375 -2.359375q-0.578125 0.234375 -1.71875 0.40625q-0.65625 0.09375 -0.921875 0.21875q-0.265625 0.109375 -0.421875 0.328125q-0.140625 0.21875 -0.140625 0.5q0 0.421875 0.3125 0.703125q0.328125 0.28125 0.9375 0.28125q0.609375 0 1.078125 -0.265625q0.484375 -0.265625 0.703125 -0.734375q0.171875 -0.359375 0.171875 -1.046875l0 -0.390625zm3.6738281 3.125l-0.984375 0l0 -8.59375l1.0625 0l0 3.0625q0.671875 -0.828125 1.703125 -0.828125q0.578125 0 1.078125 0.234375q0.515625 0.21875 0.84375 0.640625q0.34375 0.421875 0.53125 1.015625q0.1875 0.59375 0.1875 1.265625q0 1.59375 -0.796875 2.46875q-0.796875 0.875 -1.890625 0.875q-1.109375 0 -1.734375 -0.921875l0 0.78125zm-0.015625 -3.15625q0 1.109375 0.3125 1.609375q0.5 0.8125 1.34375 0.8125q0.6875 0 1.1875 -0.59375q0.515625 -0.59375 0.515625 -1.796875q0 -1.21875 -0.484375 -1.796875q-0.484375 -0.578125 -1.171875 -0.578125q-0.6875 0 -1.203125 0.609375q-0.5 0.59375 -0.5 1.734375zm5.330078 0.046875q0 -1.734375 0.953125 -2.5625q0.796875 -0.6875 1.953125 -0.6875q1.28125 0 2.09375 0.84375q0.828125 0.828125 0.828125 2.3125q0 1.203125 -0.359375 1.890625q-0.359375 0.6875 -1.0625 1.078125q-0.6875 0.375 -1.5 0.375q-1.296875 0 -2.109375 -0.828125q-0.796875 -0.84375 -0.796875 -2.421875zm1.078125 0q0 1.1875 0.515625 1.78125q0.53125 0.59375 1.3125 0.59375q0.796875 0 1.3125 -0.59375q0.515625 -0.59375 0.515625 -1.8125q0 -1.15625 -0.53125 -1.75q-0.515625 -0.59375 -1.296875 -0.59375q-0.78125 0 -1.3125 0.59375q-0.515625 0.578125 -0.515625 1.78125zm5.970703 3.109375l0 -6.21875l0.953125 0l0 0.9375q0.359375 -0.65625 0.65625 -0.859375q0.3125 -0.21875 0.6875 -0.21875q0.53125 0 1.078125 0.328125l-0.359375 0.984375q-0.390625 -0.234375 -0.765625 -0.234375q-0.359375 0 -0.640625 0.21875q-0.265625 0.203125 -0.375 0.578125q-0.1875 0.5625 -0.1875 1.21875l0 3.265625l-1.046875 0zm6.3085938 -0.9375l0.15625 0.921875q-0.453125 0.09375 -0.796875 0.09375q-0.578125 0 -0.890625 -0.171875q-0.3125 -0.1875 -0.453125 -0.484375q-0.125 -0.296875 -0.125 -1.25l0 -3.578125l-0.765625 0l0 -0.8125l0.765625 0l0 -1.546875l1.046875 -0.625l0 2.171875l1.0625 0l0 0.8125l-1.0625 0l0 3.640625q0 0.453125 0.046875 0.578125q0.0625 0.125 0.1875 0.203125q0.125 0.078125 0.359375 0.078125q0.1875 0 0.46875 -0.03125z" fill-rule="nonzero"></path><path fill="#000000" d="m54.13259 72.84834l0 0c0 -3.6007233 2.9189644 -6.519684 6.519684 -6.519684l0 0c1.7291298 0 3.387436 0.68688965 4.610115 1.9095688c1.2226791 1.2226791 1.9095688 2.880989 1.9095688 4.610115l0 0c0 3.6007233 -2.9189606 6.519684 -6.519684 6.519684l0 0c-3.6007195 0 -6.519684 -2.9189606 -6.519684 -6.519684z" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="2.0" stroke-linejoin="round" stroke-linecap="butt" d="m54.13259 72.84834l0 0c0 -3.6007233 2.9189644 -6.519684 6.519684 -6.519684l0 0c1.7291298 0 3.387436 0.68688965 4.610115 1.9095688c1.2226791 1.2226791 1.9095688 2.880989 1.9095688 4.610115l0 0c0 3.6007233 -2.9189606 6.519684 -6.519684 6.519684l0 0c-3.6007195 0 -6.519684 -2.9189606 -6.519684 -6.519684z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m61.630394 77.469734l18.236221 18.236221" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m61.630394 77.469734l13.993584 13.993584" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m74.456024 92.63126l4.376869 2.0409698l-2.0409698 -4.376869z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m199.30186 225.41402l30.29921 0l0 74.267715l-30.29921 0z" fill-rule="evenodd"></path><path fill="#1155cc" d="m213.72115 248.92174l0 -7.875l1.59375 0l0 7.875l-1.59375 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m211.40054 254.04674l2.90625 0q0.984375 0 1.5 0.15625q0.6875 0.203125 1.171875 0.71875q0.5 0.515625 0.75 1.2812347q0.265625 0.75 0.265625 1.859375q0 0.96875 -0.234375 1.671875q-0.296875 0.859375 -0.84375 1.390625q-0.421875 0.40625 -1.125 0.625q-0.515625 0.171875 -1.40625 0.171875l-2.984375 0l0 -7.8749847zm1.59375 1.328125l0 5.2187347l1.1875 0q0.65625 0 0.953125 -0.078125q0.390625 -0.09375 0.640625 -0.3125q0.265625 -0.234375 0.421875 -0.765625q0.15625 -0.53125 0.15625 -1.453125q0 -0.90625 -0.15625 -1.390625q-0.15625 -0.5 -0.453125 -0.76560974q-0.296875 -0.28125 -0.734375 -0.375q-0.328125 -0.078125 -1.3125 -0.078125l-0.703125 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m212.04059 274.92172l0 -7.8125l1.59375 0l0 6.484375l3.953125 0l0 1.328125l-5.546875 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m211.69452 287.92172l0 -7.875l5.84375 0l0 1.328125l-4.25 0l0 1.75l3.953125 0l0 1.328125l-3.953125 0l0 2.140625l4.40625 0l0 1.328125l-6.0 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m84.6785 93.29077l30.29921 0l0 83.37009l-30.29921 0z" fill-rule="evenodd"></path><path fill="#1155cc" d="m99.09778 125.90085l0 -7.875l1.59375 0l0 7.875l-1.59375 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m96.777176 131.02585l2.90625 0q0.984375 0 1.5 0.15625q0.6875 0.203125 1.171875 0.71875q0.5 0.515625 0.75 1.28125q0.265625 0.75 0.265625 1.859375q0 0.96875 -0.234375 1.671875q-0.296875 0.859375 -0.84375 1.390625q-0.421875 0.40625 -1.125 0.625q-0.515625 0.171875 -1.40625 0.171875l-2.984375 0l0 -7.875zm1.59375 1.328125l0 5.21875l1.1875 0q0.65625 0 0.953125 -0.078125q0.390625 -0.09375 0.640625 -0.3125q0.265625 -0.234375 0.421875 -0.765625q0.15625 -0.53125 0.15625 -1.453125q0 -0.90625 -0.15625 -1.390625q-0.15625 -0.5 -0.453125 -0.765625q-0.296875 -0.28125 -0.734375 -0.375q-0.328125 -0.078125 -1.3125 -0.078125l-0.703125 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m97.41722 151.90085l0 -7.8125l1.59375 0l0 6.484375l3.953125 0l0 1.328125l-5.546875 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m97.07116 164.90085l0 -7.875l5.84375 0l0 1.328125l-4.25 0l0 1.75l3.953125 0l0 1.328125l-3.953125 0l0 2.140625l4.40625 0l0 1.328125l-6.0 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m343.53543 71.43279l30.299225 0l0 146.07874l-30.299225 0z" fill-rule="evenodd"></path><path fill="#1155cc" d="m355.57797 127.75153l-1.890625 -7.875l1.640625 0l1.1875 5.40625l1.4375 -5.40625l1.890625 0l1.375 5.5l1.203125 -5.5l1.609375 0l-1.921875 7.875l-1.6875 0l-1.5625 -5.890625l-1.5625 5.890625l-1.71875 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m355.02502 136.86092q0 -1.203125 0.359375 -2.015625q0.265625 -0.609375 0.71875 -1.078125q0.46875 -0.484375 1.03125 -0.71875q0.734375 -0.3125 1.6875 -0.3125q1.75 0 2.78125 1.09375q1.046875 1.078125 1.046875 3.0q0 1.90625 -1.03125 2.984375q-1.03125 1.078125 -2.765625 1.078125q-1.765625 0 -2.796875 -1.078125q-1.03125 -1.078125 -1.03125 -2.953125zm1.625 -0.046875q0 1.328125 0.625 2.03125q0.625 0.6875 1.5625 0.6875q0.953125 0 1.5625 -0.6875q0.609375 -0.6875 0.609375 -2.0625q0 -1.34375 -0.59375 -2.015625q-0.59375 -0.671875 -1.578125 -0.671875q-0.984375 0 -1.59375 0.6875q-0.59375 0.671875 -0.59375 2.03125z" fill-rule="nonzero"></path><path fill="#1155cc" d="m355.64975 153.75154l0 -7.875l3.34375 0q1.265625 0 1.828125 0.21875q0.578125 0.203125 0.921875 0.75q0.34375 0.546875 0.34375 1.234375q0 0.890625 -0.53125 1.46875q-0.515625 0.578125 -1.546875 0.734375q0.515625 0.296875 0.84375 0.65625q0.34375 0.359375 0.90625 1.28125l0.96875 1.53125l-1.90625 0l-1.15625 -1.71875q-0.609375 -0.90625 -0.84375 -1.140625q-0.21875 -0.25 -0.46875 -0.328125q-0.25 -0.09375 -0.796875 -0.09375l-0.328125 0l0 3.28125l-1.578125 0zm1.578125 -4.546875l1.1875 0q1.140625 0 1.421875 -0.09375q0.28125 -0.09375 0.4375 -0.328125q0.171875 -0.234375 0.171875 -0.59375q0 -0.40625 -0.21875 -0.640625q-0.203125 -0.25 -0.59375 -0.3125q-0.1875 -0.03125 -1.15625 -0.03125l-1.25 0l0 2.0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m355.66537 166.75154l0 -7.875l1.578125 0l0 3.5l3.21875 -3.5l2.140625 0l-2.96875 3.0625l3.125 4.8125l-2.0625 0l-2.15625 -3.703125l-1.296875 1.328125l0 2.375l-1.578125 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m357.9547 179.75154l0 -7.875l1.59375 0l0 7.875l-1.59375 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m355.64975 192.75154l0 -7.875l1.546875 0l3.234375 5.265625l0 -5.265625l1.46875 0l0 7.875l-1.59375 0l-3.171875 -5.140625l0 5.140625l-1.484375 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m359.0094 202.86092l0 -1.328125l3.421875 0l0 3.140625q-0.5 0.46875 -1.453125 0.84375q-0.9375 0.375 -1.921875 0.375q-1.234375 0 -2.15625 -0.515625q-0.90625 -0.53125 -1.375 -1.484375q-0.453125 -0.96875 -0.453125 -2.109375q0 -1.21875 0.515625 -2.171875q0.515625 -0.96875 1.5 -1.46875q0.765625 -0.40625 1.890625 -0.40625q1.46875 0 2.28125 0.625q0.828125 0.609375 1.0625 1.703125l-1.578125 0.296875q-0.15625 -0.59375 -0.625 -0.921875q-0.453125 -0.34375 -1.140625 -0.34375q-1.046875 0 -1.65625 0.671875q-0.609375 0.65625 -0.609375 1.953125q0 1.40625 0.609375 2.109375q0.625 0.703125 1.640625 0.703125q0.5 0 1.0 -0.1875q0.5 -0.203125 0.859375 -0.484375l0 -1.0l-1.8125 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m473.15753 89.824425l30.299194 0l0 83.37008l-30.299194 0z" fill-rule="evenodd"></path><path fill="#1155cc" d="m487.5768 122.4345l0 -7.875l1.59375 0l0 7.875l-1.59375 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m485.2562 127.5595l2.90625 0q0.984375 0 1.5 0.15625q0.6875 0.203125 1.171875 0.7187424q0.5 0.515625 0.75 1.28125q0.265625 0.75 0.265625 1.859375q0 0.96875 -0.234375 1.671875q-0.296875 0.859375 -0.84375 1.390625q-0.421875 0.40625 -1.125 0.625q-0.515625 0.171875 -1.40625 0.171875l-2.984375 0l0 -7.8749924zm1.59375 1.3281174l0 5.21875l1.1875 0q0.65625 0 0.953125 -0.078125q0.390625 -0.09375 0.640625 -0.3125q0.265625 -0.234375 0.421875 -0.765625q0.15625 -0.53125 0.15625 -1.453125q0 -0.90625 -0.15625 -1.390625q-0.15625 -0.5 -0.453125 -0.765625q-0.296875 -0.28125 -0.734375 -0.375q-0.328125 -0.078125 -1.3125 -0.078125l-0.703125 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m485.89624 148.4345l0 -7.8125l1.59375 0l0 6.484375l3.953125 0l0 1.328125l-5.546875 0z" fill-rule="nonzero"></path><path fill="#1155cc" d="m485.55017 161.4345l0 -7.875l5.84375 0l0 1.328125l-4.25 0l0 1.75l3.953125 0l0 1.328125l-3.953125 0l0 2.140625l4.40625 0l0 1.328125l-6.0 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m341.56674 78.78215l37.44879 0l0 37.574806l-37.44879 0z" fill-rule="evenodd"></path><path fill="#000000" d="m350.92612 99.6434l2.0625 -0.3125q0.125 0.59375 0.53125 0.90625q0.40625 0.3125 1.125 0.3125q0.796875 0 1.203125 -0.296875q0.265625 -0.203125 0.265625 -0.546875q0 -0.234375 -0.140625 -0.390625q-0.15625 -0.140625 -0.6875 -0.265625q-2.484375 -0.546875 -3.15625 -1.0q-0.921875 -0.640625 -0.921875 -1.75q0 -1.015625 0.796875 -1.703125q0.796875 -0.6875 2.46875 -0.6875q1.609375 0 2.375 0.515625q0.78125 0.515625 1.078125 1.546875l-1.953125 0.359375q-0.125 -0.453125 -0.46875 -0.6875q-0.34375 -0.25 -0.984375 -0.25q-0.8125 0 -1.171875 0.21875q-0.234375 0.15625 -0.234375 0.421875q0 0.21875 0.203125 0.375q0.28125 0.203125 1.921875 0.578125q1.65625 0.375 2.296875 0.90625q0.65625 0.5625 0.65625 1.53125q0 1.078125 -0.90625 1.84375q-0.890625 0.765625 -2.640625 0.765625q-1.59375 0 -2.515625 -0.640625q-0.921875 -0.640625 -1.203125 -1.75zm15.385468 0.3125l0 1.90625l-7.21875 0q0.109375 -1.078125 0.6875 -2.046875q0.59375 -0.984375 2.328125 -2.578125q1.390625 -1.296875 1.703125 -1.765625q0.421875 -0.640625 0.421875 -1.25q0 -0.6875 -0.375 -1.0625q-0.359375 -0.375 -1.015625 -0.375q-0.640625 0 -1.03125 0.390625q-0.375 0.390625 -0.421875 1.296875l-2.0625 -0.21875q0.1875 -1.6875 1.15625 -2.421875q0.96875 -0.75 2.421875 -0.75q1.578125 0 2.484375 0.859375q0.921875 0.859375 0.921875 2.125q0 0.734375 -0.265625 1.390625q-0.265625 0.65625 -0.828125 1.359375q-0.375 0.484375 -1.34375 1.375q-0.96875 0.890625 -1.234375 1.1875q-0.265625 0.296875 -0.421875 0.578125l4.09375 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m470.44324 78.28346l37.448822 0l0 33.102364l-37.448822 0z" fill-rule="evenodd"></path><path fill="#000000" d="m479.8026 99.144714l2.0625 -0.3125q0.125 0.59375 0.53125 0.90625q0.40625 0.3125 1.125 0.3125q0.796875 0 1.203125 -0.296875q0.265625 -0.203125 0.265625 -0.546875q0 -0.234375 -0.140625 -0.390625q-0.15625 -0.140625 -0.6875 -0.265625q-2.484375 -0.546875 -3.15625 -1.0q-0.921875 -0.640625 -0.921875 -1.75q0 -1.015625 0.796875 -1.703125q0.796875 -0.6875 2.46875 -0.6875q1.609375 0 2.375 0.515625q0.78125 0.515625 1.078125 1.546875l-1.953125 0.359375q-0.125 -0.453125 -0.46875 -0.6875q-0.34375 -0.25 -0.984375 -0.25q-0.8125 0 -1.171875 0.21875q-0.234375 0.15625 -0.234375 0.421875q0 0.21875 0.203125 0.375q0.28125 0.203125 1.921875 0.578125q1.65625 0.375 2.296875 0.90625q0.65625 0.5625 0.65625 1.53125q0 1.078125 -0.90625 1.84375q-0.890625 0.765625 -2.640625 0.765625q-1.59375 0 -2.515625 -0.640625q-0.921875 -0.640625 -1.203125 -1.75zm8.354248 -0.625l2.0 -0.25q0.09375 0.765625 0.5 1.171875q0.421875 0.390625 1.015625 0.390625q0.640625 0 1.078125 -0.46875q0.4375 -0.484375 0.4375 -1.3125q0 -0.78125 -0.421875 -1.234375q-0.421875 -0.453125 -1.015625 -0.453125q-0.40625 0 -0.953125 0.15625l0.234375 -1.671875q0.828125 0.015625 1.265625 -0.359375q0.453125 -0.390625 0.453125 -1.03125q0 -0.53125 -0.328125 -0.859375q-0.328125 -0.328125 -0.859375 -0.328125q-0.53125 0 -0.90625 0.375q-0.359375 0.359375 -0.453125 1.0625l-1.890625 -0.3125q0.203125 -0.984375 0.59375 -1.5625q0.40625 -0.578125 1.109375 -0.90625q0.71875 -0.34375 1.609375 -0.34375q1.515625 0 2.421875 0.96875q0.765625 0.78125 0.765625 1.78125q0 1.421875 -1.546875 2.265625q0.921875 0.1875 1.46875 0.875q0.5625 0.6875 0.5625 1.671875q0 1.40625 -1.046875 2.40625q-1.03125 1.0 -2.5625 1.0q-1.453125 0 -2.421875 -0.84375q-0.953125 -0.84375 -1.109375 -2.1875z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m197.05923 202.92651l37.448822 0l0 33.102356l-37.448822 0z" fill-rule="evenodd"></path><path fill="#000000" d="m206.41861 223.78777l2.0625 -0.3125q0.125 0.59375 0.53125 0.90625q0.40625 0.3125 1.125 0.3125q0.796875 0 1.203125 -0.296875q0.265625 -0.203125 0.265625 -0.546875q0 -0.234375 -0.140625 -0.390625q-0.15625 -0.140625 -0.6875 -0.265625q-2.484375 -0.546875 -3.15625 -1.0q-0.921875 -0.640625 -0.921875 -1.75q0 -1.015625 0.796875 -1.703125q0.796875 -0.6875 2.46875 -0.6875q1.609375 0 2.375 0.515625q0.78125 0.515625 1.078125 1.546875l-1.953125 0.359375q-0.125 -0.453125 -0.46875 -0.6875q-0.34375 -0.25 -0.984375 -0.25q-0.8125 0 -1.171875 0.21875q-0.234375 0.15625 -0.234375 0.421875q0 0.21875 0.203125 0.375q0.28125 0.203125 1.921875 0.578125q1.65625 0.375 2.296875 0.90625q0.65625 0.5625 0.65625 1.53125q0 1.078125 -0.90625 1.84375q-0.890625 0.765625 -2.640625 0.765625q-1.59375 0 -2.515625 -0.640625q-0.921875 -0.640625 -1.203125 -1.75zm12.463608 2.21875l0 -2.15625l-4.390625 0l0 -1.8125l4.65625 -6.8125l1.734375 0l0 6.8125l1.328125 0l0 1.8125l-1.328125 0l0 2.15625l-2.0 0zm0 -3.96875l0 -3.671875l-2.46875 3.671875l2.46875 0z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m82.85262 78.50131l37.448822 0l0 33.102364l-37.448822 0z" fill-rule="evenodd"></path><path fill="#000000" d="m92.212 99.362564l2.0625 -0.3125q0.125 0.59375 0.53125 0.90625q0.40625 0.3125 1.125 0.3125q0.796875 0 1.203125 -0.296875q0.265625 -0.203125 0.265625 -0.546875q0 -0.234375 -0.140625 -0.390625q-0.15625 -0.140625 -0.6875 -0.265625q-2.484375 -0.546875 -3.15625 -1.0q-0.921875 -0.640625 -0.921875 -1.75q0 -1.015625 0.796875 -1.703125q0.796875 -0.6875 2.46875 -0.6875q1.609375 0 2.375 0.515625q0.78125 0.515625 1.078125 1.546875l-1.953125 0.359375q-0.125 -0.453125 -0.46875 -0.6875q-0.34375 -0.25 -0.984375 -0.25q-0.8125 0 -1.171875 0.21875q-0.234375 0.15625 -0.234375 0.421875q0 0.21875 0.203125 0.375q0.28125 0.203125 1.921875 0.578125q1.65625 0.375 2.296875 0.90625q0.65625 0.5625 0.65625 1.53125q0 1.078125 -0.90625 1.84375q-0.890625 0.765625 -2.640625 0.765625q-1.59375 0 -2.515625 -0.640625q-0.921875 -0.640625 -1.203125 -1.75zm13.697983 2.21875l-2.0625 0l0 -7.75q-1.125 1.046875 -2.65625 1.546875l0 -1.859375q0.8125 -0.265625 1.75 -1.0q0.9375 -0.734375 1.296875 -1.71875l1.671875 0l0 10.78125z" fill-rule="nonzero"></path><path fill="#000000" fill-opacity="0.0" d="m389.64304 249.45932l-9.921265 -0.06298828" fill-rule="evenodd"></path><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m389.64304 249.45932l-3.9213867 -0.024902344" fill-rule="evenodd"></path><path fill="#000000" stroke="#000000" stroke-width="1.0" stroke-linecap="butt" d="m385.73215 247.78271l-4.5484924 1.6228943l4.527527 1.6805115z" fill-rule="evenodd"></path><path fill="#000000" fill-opacity="0.0" d="m539.7638 247.77008l64.28345 0l0 58.519684l-64.28345 0z" fill-rule="evenodd"></path><path fill="#000000" d="m549.04504 260.46445l0.203125 -0.625q0.703125 0.25 1.015625 0.4375q-0.078125 -0.796875 -0.09375 -1.09375l0.640625 0q-0.015625 0.421875 -0.09375 1.078125q0.453125 -0.21875 1.03125 -0.421875l0.203125 0.625q-0.5625 0.1875 -1.09375 0.25q0.265625 0.234375 0.75 0.828125l-0.515625 0.375q-0.265625 -0.34375 -0.609375 -0.9375q-0.328125 0.609375 -0.578125 0.9375l-0.515625 -0.375q0.515625 -0.625 0.734375 -0.828125q-0.5625 -0.109375 -1.078125 -0.25zm8.532227 5.375l1.875 -6.65625l0.625 0l-1.859375 6.65625l-0.640625 0zm6.216614 -0.109375l0 -0.59375q-0.453125 0.703125 -1.3125 0.703125q-0.546875 0 -1.015625 -0.3125q-0.46875 -0.3125 -0.734375 -0.859375q-0.25 -0.546875 -0.25 -1.265625q0 -0.703125 0.234375 -1.265625q0.234375 -0.578125 0.6875 -0.875q0.46875 -0.296875 1.046875 -0.296875q0.421875 0 0.75 0.171875q0.328125 0.171875 0.53125 0.46875l0 -2.3125l0.796875 0l0 6.4375l-0.734375 0zm-2.5 -2.328125q0 0.890625 0.375 1.34375q0.375 0.4375 0.890625 0.4375q0.515625 0 0.875 -0.421875q0.375 -0.421875 0.375 -1.296875q0 -0.953125 -0.375 -1.40625q-0.375 -0.453125 -0.90625 -0.453125q-0.53125 0 -0.890625 0.4375q-0.34375 0.4375 -0.34375 1.359375zm4.359741 0q0 -1.296875 0.71875 -1.921875q0.609375 -0.515625 1.46875 -0.515625q0.96875 0 1.578125 0.625q0.609375 0.625 0.609375 1.734375q0 0.90625 -0.28125 1.421875q-0.265625 0.515625 -0.78125 0.8125q-0.515625 0.28125 -1.125 0.28125q-0.984375 0 -1.59375 -0.625q-0.59375 -0.640625 -0.59375 -1.8125zm0.8125 0q0 0.890625 0.390625 1.34375q0.390625 0.4375 0.984375 0.4375q0.59375 0 0.984375 -0.4375q0.390625 -0.453125 0.390625 -1.375q0 -0.859375 -0.390625 -1.3125q-0.390625 -0.453125 -0.984375 -0.453125q-0.59375 0 -0.984375 0.453125q-0.390625 0.4375 -0.390625 1.34375zm5.531616 2.328125l-1.421875 -4.671875l0.8125 0l0.75 2.703125l0.265625 1.0q0.015625 -0.078125 0.25 -0.96875l0.734375 -2.734375l0.8125 0l0.703125 2.71875l0.234375 0.890625l0.265625 -0.90625l0.796875 -2.703125l0.765625 0l-1.453125 4.671875l-0.8125 0l-0.75 -2.796875l-0.1875 -0.796875l-0.9375 3.59375l-0.828125 0zm5.877136 0l0 -4.671875l0.71875 0l0 0.671875q0.5 -0.765625 1.484375 -0.765625q0.421875 0 0.765625 0.15625q0.359375 0.140625 0.53125 0.390625q0.171875 0.234375 0.25 0.578125q0.046875 0.21875 0.046875 0.765625l0 2.875l-0.796875 0l0 -2.84375q0 -0.484375 -0.09375 -0.71875q-0.09375 -0.234375 -0.328125 -0.375q-0.234375 -0.15625 -0.546875 -0.15625q-0.5 0 -0.875 0.328125q-0.359375 0.3125 -0.359375 1.21875l0 2.546875l-0.796875 0zm7.779541 -5.53125l0 -0.90625l0.796875 0l0 0.90625l-0.796875 0zm0 5.53125l0 -4.671875l0.796875 0l0 4.671875l-0.796875 0zm1.7598877 -1.390625l0.78125 -0.125q0.0625 0.46875 0.359375 0.71875q0.3125 0.25 0.84375 0.25q0.546875 0 0.8125 -0.21875q0.265625 -0.21875 0.265625 -0.515625q0 -0.28125 -0.234375 -0.421875q-0.171875 -0.109375 -0.8125 -0.28125q-0.875 -0.21875 -1.21875 -0.375q-0.328125 -0.15625 -0.5 -0.4375q-0.171875 -0.28125 -0.171875 -0.625q0 -0.3125 0.140625 -0.578125q0.140625 -0.265625 0.390625 -0.453125q0.1875 -0.125 0.5 -0.21875q0.328125 -0.09375 0.6875 -0.09375q0.546875 0 0.953125 0.15625q0.421875 0.15625 0.625 0.421875q0.203125 0.265625 0.28125 0.71875l-0.78125 0.109375q-0.046875 -0.359375 -0.3125 -0.5625q-0.25 -0.203125 -0.703125 -0.203125q-0.546875 0 -0.78125 0.1875q-0.234375 0.171875 -0.234375 0.421875q0 0.15625 0.09375 0.265625q0.09375 0.140625 0.3125 0.21875q0.109375 0.046875 0.6875 0.203125q0.84375 0.21875 1.171875 0.359375q0.34375 0.140625 0.53125 0.421875q0.1875 0.265625 0.1875 0.671875q0 0.40625 -0.234375 0.75q-0.234375 0.34375 -0.671875 0.546875q-0.421875 0.1875 -0.984375 0.1875q-0.90625 0 -1.390625 -0.375q-0.46875 -0.390625 -0.59375 -1.125z" fill-rule="nonzero"></path><path fill="#000000" d="m549.04504 275.33945l0.78125 -0.125q0.0625 0.46875 0.359375 0.71875q0.3125 0.25 0.84375 0.25q0.546875 0 0.8125 -0.21875q0.265625 -0.21875 0.265625 -0.515625q0 -0.28125 -0.234375 -0.421875q-0.171875 -0.109375 -0.8125 -0.28125q-0.875 -0.21875 -1.21875 -0.375q-0.328125 -0.15625 -0.5 -0.4375q-0.171875 -0.28125 -0.171875 -0.625q0 -0.3125 0.140625 -0.578125q0.140625 -0.265625 0.390625 -0.453125q0.1875 -0.125 0.5 -0.21875q0.328125 -0.09375 0.6875 -0.09375q0.546875 0 0.953125 0.15625q0.421875 0.15625 0.625 0.421875q0.203125 0.265625 0.28125 0.71875l-0.78125 0.109375q-0.046875 -0.359375 -0.3125 -0.5625q-0.25 -0.203125 -0.703125 -0.203125q-0.546875 0 -0.78125 0.1875q-0.234375 0.171875 -0.234375 0.421875q0 0.15625 0.09375 0.265625q0.09375 0.140625 0.3125 0.21875q0.109375 0.046875 0.6875 0.203125q0.84375 0.21875 1.171875 0.359375q0.34375 0.140625 0.53125 0.421875q0.1875 0.265625 0.1875 0.671875q0 0.40625 -0.234375 0.75q-0.234375 0.34375 -0.671875 0.546875q-0.421875 0.1875 -0.984375 0.1875q-0.90625 0 -1.390625 -0.375q-0.46875 -0.390625 -0.59375 -1.125zm8.1640625 -0.109375l0.828125 0.09375q-0.203125 0.71875 -0.734375 1.125q-0.515625 0.390625 -1.328125 0.390625q-1.015625 0 -1.625 -0.625q-0.59375 -0.640625 -0.59375 -1.78125q0 -1.171875 0.609375 -1.8125q0.609375 -0.65625 1.578125 -0.65625q0.9375 0 1.515625 0.640625q0.59375 0.625 0.59375 1.78125q0 0.078125 0 0.21875l-3.484375 0q0.046875 0.765625 0.4375 1.171875q0.390625 0.40625 0.984375 0.40625q0.4375 0 0.734375 -0.21875q0.3125 -0.234375 0.484375 -0.734375zm-2.59375 -1.28125l2.609375 0q-0.046875 -0.59375 -0.296875 -0.890625q-0.375 -0.453125 -0.984375 -0.453125q-0.546875 0 -0.921875 0.375q-0.359375 0.359375 -0.40625 0.96875zm4.594116 2.78125l0 -4.671875l0.71875 0l0 0.671875q0.5 -0.765625 1.484375 -0.765625q0.421875 0 0.765625 0.15625q0.359375 0.140625 0.53125 0.390625q0.171875 0.234375 0.25 0.578125q0.046875 0.21875 0.046875 0.765625l0 2.875l-0.796875 0l0 -2.84375q0 -0.484375 -0.09375 -0.71875q-0.09375 -0.234375 -0.328125 -0.375q-0.234375 -0.15625 -0.546875 -0.15625q-0.5 0 -0.875 0.328125q-0.359375 0.3125 -0.359375 1.21875l0 2.546875l-0.796875 0zm6.922241 -0.703125l0.109375 0.6875q-0.34375 0.078125 -0.59375 0.078125q-0.4375 0 -0.671875 -0.140625q-0.234375 -0.140625 -0.34375 -0.359375q-0.09375 -0.21875 -0.09375 -0.921875l0 -2.6875l-0.578125 0l0 -0.625l0.578125 0l0 -1.15625l0.796875 -0.46875l0 1.625l0.796875 0l0 0.625l-0.796875 0l0 2.71875q0 0.34375 0.03125 0.4375q0.046875 0.09375 0.140625 0.15625q0.09375 0.0625 0.265625 0.0625q0.140625 0 0.359375 -0.03125zm3.1520386 -1.625q0 -1.296875 0.71875 -1.921875q0.609375 -0.515625 1.46875 -0.515625q0.96875 0 1.578125 0.625q0.609375 0.625 0.609375 1.734375q0 0.90625 -0.28125 1.421875q-0.265625 0.515625 -0.78125 0.8125q-0.515625 0.28125 -1.125 0.28125q-0.984375 0 -1.59375 -0.625q-0.59375 -0.640625 -0.59375 -1.8125zm0.8125 0q0 0.890625 0.390625 1.34375q0.390625 0.4375 0.984375 0.4375q0.59375 0 0.984375 -0.4375q0.390625 -0.453125 0.390625 -1.375q0 -0.859375 -0.390625 -1.3125q-0.390625 -0.453125 -0.984375 -0.453125q-0.59375 0 -0.984375 0.453125q-0.390625 0.4375 -0.390625 1.34375zm4.672241 2.328125l0 -4.671875l0.71875 0l0 0.671875q0.5 -0.765625 1.484375 -0.765625q0.421875 0 0.765625 0.15625q0.359375 0.140625 0.53125 0.390625q0.171875 0.234375 0.25 0.578125q0.046875 0.21875 0.046875 0.765625l0 2.875l-0.796875 0l0 -2.84375q0 -0.484375 -0.09375 -0.71875q-0.09375 -0.234375 -0.328125 -0.375q-0.234375 -0.15625 -0.546875 -0.15625q-0.5 0 -0.875 0.328125q-0.359375 0.3125 -0.359375 1.21875l0 2.546875l-0.796875 0zm5.172241 0l0 -6.4375l0.78125 0l0 6.4375l-0.78125 0zm2.0568237 1.796875l-0.09375 -0.734375q0.265625 0.0625 0.453125 0.0625q0.265625 0 0.421875 -0.09375q0.15625 -0.078125 0.265625 -0.234375q0.0625 -0.125 0.234375 -0.59375q0.03125 -0.0625 0.078125 -0.1875l-1.78125 -4.6875l0.859375 0l0.96875 2.703125q0.1875 0.515625 0.34375 1.078125q0.125 -0.53125 0.3125 -1.0625l1.0 -2.71875l0.796875 0l-1.78125 4.75q-0.28125 0.765625 -0.4375 1.0625q-0.21875 0.390625 -0.484375 0.5625q-0.265625 0.1875 -0.65625 0.1875q-0.21875 0 -0.5 -0.09375zm7.2869873 -7.328125l0 -0.90625l0.796875 0l0 0.90625l-0.796875 0zm0 5.53125l0 -4.671875l0.796875 0l0 4.671875l-0.796875 0zm2.2598877 0l0 -4.046875l-0.703125 0l0 -0.625l0.703125 0l0 -0.484375q0 -0.484375 0.078125 -0.703125q0.125 -0.3125 0.40625 -0.5q0.296875 -0.1875 0.8125 -0.1875q0.328125 0 0.734375 0.078125l-0.125 0.6875q-0.234375 -0.046875 -0.453125 -0.046875q-0.359375 0 -0.515625 0.15625q-0.15625 0.15625 -0.15625 0.578125l0 0.421875l0.921875 0l0 0.625l-0.921875 0l0 4.046875l-0.78125 0z" fill-rule="nonzero"></path><path fill="#000000" d="m549.35754 289.51132l0 -6.453125l0.71875 0l0 0.609375q0.25 -0.359375 0.5625 -0.53125q0.328125 -0.171875 0.796875 -0.171875q0.59375 0 1.046875 0.3125q0.453125 0.296875 0.6875 0.859375q0.234375 0.5625 0.234375 1.21875q0 0.71875 -0.265625 1.296875q-0.25 0.578125 -0.734375 0.890625q-0.484375 0.296875 -1.03125 0.296875q-0.390625 0 -0.703125 -0.171875q-0.3125 -0.171875 -0.515625 -0.421875l0 2.265625l-0.796875 0zm0.71875 -4.09375q0 0.90625 0.359375 1.34375q0.375 0.421875 0.890625 0.421875q0.515625 0 0.890625 -0.4375q0.390625 -0.453125 0.390625 -1.390625q0 -0.890625 -0.375 -1.328125q-0.359375 -0.453125 -0.875 -0.453125q-0.5 0 -0.890625 0.484375q-0.390625 0.46875 -0.390625 1.359375zm4.453491 2.3125l0 -4.671875l0.71875 0l0 0.71875q0.265625 -0.5 0.5 -0.65625q0.234375 -0.15625 0.515625 -0.15625q0.390625 0 0.8125 0.25l-0.28125 0.734375q-0.28125 -0.171875 -0.578125 -0.171875q-0.25 0 -0.46875 0.15625q-0.203125 0.15625 -0.296875 0.4375q-0.125 0.421875 -0.125 0.921875l0 2.4375l-0.796875 0zm2.8250732 -2.328125q0 -1.296875 0.71875 -1.921875q0.609375 -0.515625 1.46875 -0.515625q0.96875 0 1.578125 0.625q0.609375 0.625 0.609375 1.734375q0 0.90625 -0.28125 1.421875q-0.265625 0.515625 -0.78125 0.8125q-0.515625 0.28125 -1.125 0.28125q-0.984375 0 -1.59375 -0.625q-0.59375 -0.640625 -0.59375 -1.8125zm0.8125 0q0 0.890625 0.390625 1.34375q0.390625 0.4375 0.984375 0.4375q0.59375 0 0.984375 -0.4375q0.390625 -0.453125 0.390625 -1.375q0 -0.859375 -0.390625 -1.3125q-0.390625 -0.453125 -0.984375 -0.453125q-0.59375 0 -0.984375 0.453125q-0.390625 0.4375 -0.390625 1.34375zm7.719116 0.625l0.78125 0.09375q-0.125 0.8125 -0.65625 1.265625q-0.53125 0.453125 -1.296875 0.453125q-0.953125 0 -1.53125 -0.625q-0.578125 -0.625 -0.578125 -1.796875q0 -0.765625 0.25 -1.328125q0.25 -0.5625 0.75 -0.84375q0.515625 -0.28125 1.125 -0.28125q0.75 0 1.234375 0.390625q0.484375 0.375 0.625 1.078125l-0.765625 0.125q-0.109375 -0.46875 -0.390625 -0.703125q-0.28125 -0.25 -0.671875 -0.25q-0.609375 0 -0.984375 0.4375q-0.359375 0.421875 -0.359375 1.34375q0 0.953125 0.359375 1.375q0.359375 0.421875 0.9375 0.421875q0.46875 0 0.78125 -0.28125q0.3125 -0.28125 0.390625 -0.875zm4.8046875 0.203125l0.828125 0.09375q-0.203125 0.71875 -0.734375 1.125q-0.515625 0.390625 -1.328125 0.390625q-1.015625 0 -1.625 -0.625q-0.59375 -0.640625 -0.59375 -1.78125q0 -1.171875 0.609375 -1.8125q0.609375 -0.65625 1.578125 -0.65625q0.9375 0 1.515625 0.640625q0.59375 0.625 0.59375 1.78125q0 0.078125 0 0.21875l-3.484375 0q0.046875 0.765625 0.4375 1.171875q0.390625 0.40625 0.984375 0.40625q0.4375 0 0.734375 -0.21875q0.3125 -0.234375 0.484375 -0.734375zm-2.59375 -1.28125l2.609375 0q-0.046875 -0.59375 -0.296875 -0.890625q-0.375 -0.453125 -0.984375 -0.453125q-0.546875 0 -0.921875 0.375q-0.359375 0.359375 -0.40625 0.96875zm4.281616 1.390625l0.78125 -0.125q0.0625 0.46875 0.359375 0.71875q0.3125 0.25 0.84375 0.25q0.546875 0 0.8125 -0.21875q0.265625 -0.21875 0.265625 -0.515625q0 -0.28125 -0.234375 -0.421875q-0.171875 -0.109375 -0.8125 -0.28125q-0.875 -0.21875 -1.21875 -0.375q-0.328125 -0.15625 -0.5 -0.4375q-0.171875 -0.28125 -0.171875 -0.625q0 -0.3125 0.140625 -0.578125q0.140625 -0.265625 0.390625 -0.453125q0.1875 -0.125 0.5 -0.21875q0.328125 -0.09375 0.6875 -0.09375q0.546875 0 0.953125 0.15625q0.421875 0.15625 0.625 0.421875q0.203125 0.265625 0.28125 0.71875l-0.78125 0.109375q-0.046875 -0.359375 -0.3125 -0.5625q-0.25 -0.203125 -0.703125 -0.203125q-0.546875 0 -0.78125 0.1875q-0.234375 0.171875 -0.234375 0.421875q0 0.15625 0.09375 0.265625q0.09375 0.140625 0.3125 0.21875q0.109375 0.046875 0.6875 0.203125q0.84375 0.21875 1.171875 0.359375q0.34375 0.140625 0.53125 0.421875q0.1875 0.265625 0.1875 0.671875q0 0.40625 -0.234375 0.75q-0.234375 0.34375 -0.671875 0.546875q-0.421875 0.1875 -0.984375 0.1875q-0.90625 0 -1.390625 -0.375q-0.46875 -0.390625 -0.59375 -1.125zm4.6640625 0l0.78125 -0.125q0.0625 0.46875 0.359375 0.71875q0.3125 0.25 0.84375 0.25q0.546875 0 0.8125 -0.21875q0.265625 -0.21875 0.265625 -0.515625q0 -0.28125 -0.234375 -0.421875q-0.171875 -0.109375 -0.8125 -0.28125q-0.875 -0.21875 -1.21875 -0.375q-0.328125 -0.15625 -0.5 -0.4375q-0.171875 -0.28125 -0.171875 -0.625q0 -0.3125 0.140625 -0.578125q0.140625 -0.265625 0.390625 -0.453125q0.1875 -0.125 0.5 -0.21875q0.328125 -0.09375 0.6875 -0.09375q0.546875 0 0.953125 0.15625q0.421875 0.15625 0.625 0.421875q0.203125 0.265625 0.28125 0.71875l-0.78125 0.109375q-0.046875 -0.359375 -0.3125 -0.5625q-0.25 -0.203125 -0.703125 -0.203125q-0.546875 0 -0.78125 0.1875q-0.234375 0.171875 -0.234375 0.421875q0 0.15625 0.09375 0.265625q0.09375 0.140625 0.3125 0.21875q0.109375 0.046875 0.6875 0.203125q0.84375 0.21875 1.171875 0.359375q0.34375 0.140625 0.53125 0.421875q0.1875 0.265625 0.1875 0.671875q0 0.40625 -0.234375 0.75q-0.234375 0.34375 -0.671875 0.546875q-0.421875 0.1875 -0.984375 0.1875q-0.90625 0 -1.390625 -0.375q-0.46875 -0.390625 -0.59375 -1.125zm7.5682373 -4.140625l0 -0.90625l0.796875 0l0 0.90625l-0.796875 0zm0 5.53125l0 -4.671875l0.796875 0l0 4.671875l-0.796875 0zm1.7598877 -1.390625l0.78125 -0.125q0.0625 0.46875 0.359375 0.71875q0.3125 0.25 0.84375 0.25q0.546875 0 0.8125 -0.21875q0.265625 -0.21875 0.265625 -0.515625q0 -0.28125 -0.234375 -0.421875q-0.171875 -0.109375 -0.8125 -0.28125q-0.875 -0.21875 -1.21875 -0.375q-0.328125 -0.15625 -0.5 -0.4375q-0.171875 -0.28125 -0.171875 -0.625q0 -0.3125 0.140625 -0.578125q0.140625 -0.265625 0.390625 -0.453125q0.1875 -0.125 0.5 -0.21875q0.328125 -0.09375 0.6875 -0.09375q0.546875 0 0.953125 0.15625q0.421875 0.15625 0.625 0.421875q0.203125 0.265625 0.28125 0.71875l-0.78125 0.109375q-0.046875 -0.359375 -0.3125 -0.5625q-0.25 -0.203125 -0.703125 -0.203125q-0.546875 0 -0.78125 0.1875q-0.234375 0.171875 -0.234375 0.421875q0 0.15625 0.09375 0.265625q0.09375 0.140625 0.3125 0.21875q0.109375 0.046875 0.6875 0.203125q0.84375 0.21875 1.171875 0.359375q0.34375 0.140625 0.53125 0.421875q0.1875 0.265625 0.1875 0.671875q0 0.40625 -0.234375 0.75q-0.234375 0.34375 -0.671875 0.546875q-0.421875 0.1875 -0.984375 0.1875q-0.90625 0 -1.390625 -0.375q-0.46875 -0.390625 -0.59375 -1.125z" fill-rule="nonzero"></path><path fill="#000000" d="m549.35754 298.73007l0 -4.671875l0.703125 0l0 0.65625q0.21875 -0.34375 0.578125 -0.546875q0.375 -0.203125 0.84375 -0.203125q0.515625 0 0.84375 0.21875q0.328125 0.203125 0.46875 0.59375q0.5625 -0.8125 1.4375 -0.8125q0.703125 0 1.078125 0.390625q0.375 0.375 0.375 1.171875l0 3.203125l-0.796875 0l0 -2.9375q0 -0.484375 -0.078125 -0.6875q-0.0625 -0.203125 -0.265625 -0.328125q-0.203125 -0.140625 -0.484375 -0.140625q-0.484375 0 -0.8125 0.328125q-0.328125 0.328125 -0.328125 1.046875l0 2.71875l-0.796875 0l0 -3.03125q0 -0.53125 -0.1875 -0.796875q-0.1875 -0.265625 -0.625 -0.265625q-0.34375 0 -0.625 0.1875q-0.28125 0.171875 -0.40625 0.515625q-0.125 0.328125 -0.125 0.96875l0 2.421875l-0.796875 0zm7.4735107 -2.328125q0 -1.296875 0.71875 -1.921875q0.609375 -0.515625 1.46875 -0.515625q0.96875 0 1.578125 0.625q0.609375 0.625 0.609375 1.734375q0 0.90625 -0.28125 1.421875q-0.265625 0.515625 -0.78125 0.8125q-0.515625 0.28125 -1.125 0.28125q-0.984375 0 -1.59375 -0.625q-0.59375 -0.640625 -0.59375 -1.8125zm0.8125 0q0 0.890625 0.390625 1.34375q0.390625 0.4375 0.984375 0.4375q0.59375 0 0.984375 -0.4375q0.390625 -0.453125 0.390625 -1.375q0 -0.859375 -0.390625 -1.3125q-0.390625 -0.453125 -0.984375 -0.453125q-0.59375 0 -0.984375 0.453125q-0.390625 0.4375 -0.390625 1.34375zm4.672241 2.328125l0 -4.671875l0.71875 0l0 0.671875q0.5 -0.765625 1.484375 -0.765625q0.421875 0 0.765625 0.15625q0.359375 0.140625 0.53125 0.390625q0.171875 0.234375 0.25 0.578125q0.046875 0.21875 0.046875 0.765625l0 2.875l-0.796875 0l0 -2.84375q0 -0.484375 -0.09375 -0.71875q-0.09375 -0.234375 -0.328125 -0.375q-0.234375 -0.15625 -0.546875 -0.15625q-0.5 0 -0.875 0.328125q-0.359375 0.3125 -0.359375 1.21875l0 2.546875l-0.796875 0zm5.187866 -5.53125l0 -0.90625l0.796875 0l0 0.90625l-0.796875 0zm0 5.53125l0 -4.671875l0.796875 0l0 4.671875l-0.796875 0zm3.8068237 -0.703125l0.109375 0.6875q-0.34375 0.078125 -0.59375 0.078125q-0.4375 0 -0.671875 -0.140625q-0.234375 -0.140625 -0.34375 -0.359375q-0.09375 -0.21875 -0.09375 -0.921875l0 -2.6875l-0.578125 0l0 -0.625l0.578125 0l0 -1.15625l0.796875 -0.46875l0 1.625l0.796875 0l0 0.625l-0.796875 0l0 2.71875q0 0.34375 0.03125 0.4375q0.046875 0.09375 0.140625 0.15625q0.09375 0.0625 0.265625 0.0625q0.140625 0 0.359375 -0.03125zm0.56036377 -1.625q0 -1.296875 0.71875 -1.921875q0.609375 -0.515625 1.46875 -0.515625q0.96875 0 1.578125 0.625q0.609375 0.625 0.609375 1.734375q0 0.90625 -0.28125 1.421875q-0.265625 0.515625 -0.78125 0.8125q-0.515625 0.28125 -1.125 0.28125q-0.984375 0 -1.59375 -0.625q-0.59375 -0.640625 -0.59375 -1.8125zm0.8125 0q0 0.890625 0.390625 1.34375q0.390625 0.4375 0.984375 0.4375q0.59375 0 0.984375 -0.4375q0.390625 -0.453125 0.390625 -1.375q0 -0.859375 -0.390625 -1.3125q-0.390625 -0.453125 -0.984375 -0.453125q-0.59375 0 -0.984375 0.453125q-0.390625 0.4375 -0.390625 1.34375zm4.656616 2.328125l0 -4.671875l0.71875 0l0 0.71875q0.265625 -0.5 0.5 -0.65625q0.234375 -0.15625 0.515625 -0.15625q0.390625 0 0.8125 0.25l-0.28125 0.734375q-0.28125 -0.171875 -0.578125 -0.171875q-0.25 0 -0.46875 0.15625q-0.203125 0.15625 -0.296875 0.4375q-0.125 0.421875 -0.125 0.921875l0 2.4375l-0.796875 0zm6.3095093 -1.5l0.828125 0.09375q-0.203125 0.71875 -0.734375 1.125q-0.515625 0.390625 -1.328125 0.390625q-1.015625 0 -1.625 -0.625q-0.59375 -0.640625 -0.59375 -1.78125q0 -1.171875 0.609375 -1.8125q0.609375 -0.65625 1.578125 -0.65625q0.9375 0 1.515625 0.640625q0.59375 0.625 0.59375 1.78125q0 0.078125 0 0.21875l-3.484375 0q0.046875 0.765625 0.4375 1.171875q0.390625 0.40625 0.984375 0.40625q0.4375 0 0.734375 -0.21875q0.3125 -0.234375 0.484375 -0.734375zm-2.59375 -1.28125l2.609375 0q-0.046875 -0.59375 -0.296875 -0.890625q-0.375 -0.453125 -0.984375 -0.453125q-0.546875 0 -0.921875 0.375q-0.359375 0.359375 -0.40625 0.96875zm7.625366 2.78125l0 -0.59375q-0.453125 0.703125 -1.3125 0.703125q-0.546875 0 -1.015625 -0.3125q-0.46875 -0.3125 -0.734375 -0.859375q-0.25 -0.546875 -0.25 -1.265625q0 -0.703125 0.234375 -1.265625q0.234375 -0.578125 0.6875 -0.875q0.46875 -0.296875 1.046875 -0.296875q0.421875 0 0.75 0.171875q0.328125 0.171875 0.53125 0.46875l0 -2.3125l0.796875 0l0 6.4375l-0.734375 0zm-2.5 -2.328125q0 0.890625 0.375 1.34375q0.375 0.4375 0.890625 0.4375q0.515625 0 0.875 -0.421875q0.375 -0.421875 0.375 -1.296875q0 -0.953125 -0.375 -1.40625q-0.375 -0.453125 -0.90625 -0.453125q-0.53125 0 -0.890625 0.4375q-0.34375 0.4375 -0.34375 1.359375z" fill-rule="nonzero"></path></g></svg> | |
| <p> | |
| Web Prolog comes with a set of built-in predicates that allows a client to spawn a local or remote pengine (<code>pengine_spawn/1-2</code>), and to send the type of messages in bold to it (<code>pengine_ask/2-3</code>, <code>pengine_next/1-2</code>, <code>pengine_stop/1</code>, <code>pengine_respond/2</code>, <code>pengine_abort/1</code> and <code>pengine_exit/1</code>). Under the hood, those predicates are using <code>!/2</code> and <code>receive/1-2</code> to do their thing so they are mostly there for convenience and for shielding the programmer from the raw details of the protocol. Here's how to spawn a local pengine: | |
| </p> | |
| <pre id="pengine-simple1"> | |
| ?- pengine_spawn(Pid, [exit(false)]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-simple1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| The value of the <code>exit</code> option is either <code>true</code> (default) or <code>false</code>). If <code>false</code>, it tells the pengine to run a full session, i.e. to <i>not</i> terminate once the first query that we are going to ask it has run to completion. In other words, this is an option that allows us to switch from running a one-shot encapsulated search task into running a full encapsulated Prolog session. | |
| </p> | |
| <p> | |
| The pengine is now initialised and the protocol is in state <em>s1</em>. Here's how to ask the pengine a query: | |
| </p> | |
| <pre id="pengine-simple2"> | |
| ?- pengine_ask($Pid, member(X,[a,b,c])), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-simple2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| This changed the state of the protocol from <em>s1</em> to state <em>s2</em> and then to state <em>s4</em>. A <code>success</code> message was sent back to the client during the transition from <em>s2</em> to <em>s4</em>. The reason we end up in <em>s4</em> rather than in <em>s1</em> is that the message (which was "produced" inside <em>s2</em>) has <code>true</code> in its third argument, indicating that more solutions may exist. | |
| </p> | |
| <p> | |
| Here's how to use <code>pengine_next/1</code> to request the next solution to our query: | |
| </p> | |
| <pre id="pengine-simple3"> | |
| ?- pengine_next($Pid), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-simple3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| The protocol transitioned first to state <em>s2</em>, and then back to <em>s4</em> again. | |
| </p> | |
| <p> | |
| When we don't want more answers, we just need to call <code>pengine_stop/1</code>: | |
| </p> | |
| <pre id="pengine-simple4"> | |
| ?- pengine_stop($Pid), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-simple4")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| We are back in state <em>s1</em> and the pengine is ready to accept new queries. Now, let's try this: | |
| </p> | |
| <pre id="pengine-output1"> | |
| ?- pengine_ask($Pid, pengine_output(p(a))), | |
| receive({Message1 -> true}), | |
| receive({Message2 -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-output1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| This took the protocol to state <em>s2</em> and then over the transition labeled /output and back to state <em>s2</em> and finally to <em>s1</em> again. Two messages were sent back to the client: one message representing the output produced by the call to <code>pengine_output/1</code>, and one message representing the success of the call itself. | |
| </p> | |
| <p> | |
| Next, let's try this: | |
| </p> | |
| <pre id="pengine-input1"> | |
| ?- pengine_ask($Pid, pengine_input(myprompt, Answer)), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-input1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| This changed the state of the protocol to <em>s2</em> and then directly to <em>s3</em> while sending back a <code>prompt</code> term. It now waits in state <em>s3</em>. | |
| </p> | |
| <p> | |
| This is how we can respond to the prompt: | |
| </p> | |
| <pre id="pengine-input2"> | |
| ?- pengine_respond($Pid, hello), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-input2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| The protocol transitioned to state <em>s2</em> and then directly to state <em>s1</em> while sending back a <code>success</code> term to the client. | |
| </p> | |
| <p> | |
| Having spawned a process running a goal, it sometimes happens that it loops indefinitely, so that we may | |
| need to terminate it by force. To see how, let's ask a query that takes a while to terminate ...</p> | |
| <pre id="pengine-abort1"> | |
| ?- pengine_ask($Pid, forall(between(1,30, N), (io:writeln(N), sleep(1)))). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-abort1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p>... just to be able to show how to abort it:</p> | |
| <pre id="pengine-abort2"> | |
| ?- pengine_abort($Pid), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-abort2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="alert alert-danger"> | |
| <strong>BUG!</strong> The abort generated the wrong response. Should have been | |
| <code>abort(<pid>)</code>. | |
| </div> | |
| <p> | |
| Note that aborting the running query doesn't kill the pengine. It's still alive, ready to accept new queries. To kill a pengine from the outside, you'd need to call <code>pengine_exit/2</code> (or just <code>exit/2</code>). To do it from the inside, i.e. to make it commit suicide, you would need to use <code>pengine_exit/1</code> (or just <code>exit/1</code>). More on this later. | |
| </p> | |
| <h3>Pengines deferring messages</h3> | |
| <p> | |
| One consequence of the message-deferring behaviour of <code>receive/1-2</code> described earlier is that messages, to some extent, are allowed to arrive to the mailbox of a pengine in the "wrong" order. Let's look at an example where we first spawn a new pengine: | |
| </p> | |
| <pre id="pengine-deferred1"> | |
| ?- pengine_spawn(Pid). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-deferred1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p>Now, let's make a deliberate "mistake" and call <code>pengine_next/1</code> to request the next solution to a query that hasn't yet been asked:</p> | |
| <pre id="pengine-deferred2"> | |
| ?- pengine_next($Pid). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-deferred2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p>We use <code>flush/0</code> to inspect the result:</p> | |
| <pre id="pengine-deferred3"> | |
| ?- flush. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-deferred3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="row"> | |
| <div class="col-xs-3"> | |
| <img src="img/no-mail.png" alt=""> | |
| </div> | |
| <div class="col-xs-9"> | |
| <p> | |
| Right, the mailbox was empty since the protocol obviously wasn't in a state where it could react on the <code>next</code> message. The message was therefore deferred and it's not until <code>pengine_ask/3</code> is called and the protocol changes states that the message <code>next</code> can have an effect. Let's now ask the pengine a query ... | |
| </p> | |
| </div> | |
| </div> | |
| <pre id="pengine-deferred4"> | |
| ?- pengine_ask($Pid, member(X,[a,b,c])). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-deferred4")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p>... and use <code>flush/0</code> to inspect the result:</p> | |
| <pre id="pengine-deferred5"> | |
| ?- flush. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-deferred5")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Yes, that's right – now two messages appeared in the mailbox! | |
| </p> | |
| <h3>Pengines and options</h3> | |
| <p> | |
| A pengine is a programming abstraction built on top of an actor and as a consequence | |
| <code>pengine_spawn/2</code> inherits the options that <code>spawn/3</code> offers. Here's how to use | |
| some of them when creating a monitored session running remotely: | |
| </p> | |
| <pre id="pengine-advanced1"> | |
| ?- pengine_spawn(Pid, [ | |
| exit(false), | |
| monitor(true), | |
| node('{{host}}'), | |
| src_list([p(a),p(b),p(c),p(d),p(e),p(f),p(g)]) | |
| ]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-advanced1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| The predicate <code>pengine_ask/3</code> introduces two new options, | |
| <code>template</code> and <code>limit</code>. Here's how they may be used | |
| when querying the pengine just created: | |
| </p> | |
| <pre id="pengine-advanced2"> | |
| ?- pengine_ask($Pid, p(X), [ | |
| template(X), | |
| limit(2) | |
| ]), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-advanced2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| The predicate <code>pengine_next/2</code> is used for retrieving more | |
| solutions, and we will get two of them this time too, just because the previous | |
| call to <code>pengine_ask/3</code> set this limit: | |
| </p> | |
| <pre id="pengine-advanced3"> | |
| ?- pengine_next($Pid), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-advanced3")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| But <code>pengine_next/2</code> supports the option <code>limit</code> as well, so that we are allowed to increase (or decrease) the number of solutions that we would like to get: | |
| </p> | |
| <pre id="pengine-advanced4"> | |
| ?- pengine_next($Pid, [ | |
| limit(4) | |
| ]), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-advanced4")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| At some point, we may want to terminate the session. This is where the | |
| <code>exit/2</code> predicate comes in. Since our pengine is monitored we can expect a <code>down</code> message to appear in our top-level mailbox: | |
| </p> | |
| <pre id="pengine-advanced5"> | |
| ?- exit($Pid, please_die), | |
| receive({Message -> true}). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengine-advanced5")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="alert alert-success"> | |
| <h4>SWISH</h4> | |
| <p> | |
| How about a short break to take a look at a more sophisticated online Prolog IDE? Head over to <a target="_blank" href="https://swish.swi-prolog.org">SWISH</a>, which is a mature web-based IDE to Prolog, featuring semantic source code colouring, Prolog notebooks, tools for collaboration, and other niceties. | |
| </p> | |
| <p> | |
| In September 2017, SWISH had been online for three years. It is extensively being used for Prolog education. At the time of writing this, the main server now has 35,420 programs stored with 68,806 revisions. Usage is strongly correlated with academic holidays, ranging from 36,415 queries per week | |
| (July 31 - August 6, 2017) to 125,946 (May 7-14, 2017). The popularity of the Github repository, 198 stars and 48 forks compared to the main SWI-Prolog repository with 223 stars and 53 forks is another indication of the impact. (To be compared with Erlang's 6,374 stars and 1,708 forks.) | |
| </p> | |
| <p> | |
| Please understand, though, that the present version of SWISH doesn't implement Web Prolog, but something slightly different. In the future, we plan to integrate Web Prolog with SWISH. The traditional shell will be one of the tools on offer, selectable from a tool bar along with the present one. This tutorial will be rewritten as a Prolog notebook. However, since SWISH has a large number of users already, it wouldn't make sense to change the underlying language until all problems have been sorted out. | |
| </p> | |
| </div> | |
| <!-- | |
| <h3>Pengines and input/output</h3> | |
| <p> | |
| This is how I/O works in a pengine: | |
| </p> | |
| <pre id="pengines-io1"> | |
| ?- pengine_spawn(Pid), | |
| pengine_ask(Pid, io:writeln(hello)), | |
| pengine_ask(Pid, pengine_output(goodbye)). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengines-io1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p>Let's flush the top-level mailbox:</p> | |
| <pre id="pengines-io2"> | |
| ?- flush. | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pengines-io2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| --> | |
| <h2>Using the Web APIs</h2> | |
| <p> | |
| A Web Prolog runtime system – or what we have referred to as a <i>node</i> – is equipped with a small but comprehensive set of web APIs, using both WebSocket and HTTP as transport protocols, over which a client can run Web Prolog programs defined by the owner of the client, the owner of the node, or a combination of both. In this section we'll take a closer look at these APIs and show how they can be used. | |
| </p> | |
| <p> | |
| The web APIs are not an afterthought. They are extremely important. Indeed, if we did not use HTTP or WebSocket, we would not be on the Web. We use both, and the whole distribution layer of Web Prolog depends on them, for openness, as well as for security. The web APIs must be capable of everything that the predicate APIs are capable of, or else they wouldn't be able to support the distributed programming model of Web Prolog. | |
| </p> | |
| <h3>Using the WebSocket API</h3> | |
| <p> | |
| A Web Prolog node supports an <i>asynchronous</i> and <i>stateful</i> web API that relies on the WebSocket transport. Offering an almost total control over a pengine it currently serves the shell (which despite its apparent simplicity is a fairly demanding web application) as well as the predicate API with its <code>node</code> option to <code>spawn/2-3</code>and its network transparent send primitive <code>!/2</code>. | |
| </p> | |
| <p> | |
| For an example of how the bare WebSocket API can be used from a non-Prolog programming language, you need to look no further than the JavaScript source code implementing the shell. It's a bit too messy for a tutorial though. (The implementation relies a lot on a library called <a href="http://terminal.jcubic.pl">jQuery Terminal</a>. Without that, it would have been a lot messier.) | |
| </p> | |
| <p> | |
| We have prepared two much simpler (and self-contained but useless) applications that you can try by selecting the links below. Upon page load, the first application will create a WebSocket connection to the node, spawn a pengine there, and (when having received a pid) ask the query <code>ancestor_descendant(mike, Who)</code>. The <code>limit</code> option will be set to <code>2</code>. Here's the complete HTML and JavaScript source code for this application: | |
| </p> | |
| <pre> | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"/> | |
| <title>Example 1</title> | |
| <script> | |
| var ws = new WebSocket('ws://localhost:3060/ws','pcp-0.2'); | |
| ws.onopen = function (message) { | |
| ws.send(JSON.stringify({ | |
| command: 'pengine_spawn' | |
| })); | |
| }; | |
| ws.onmessage = function (message) { | |
| var event = JSON.parse(message.data); | |
| if (event.type == 'spawned') { | |
| ws.send(JSON.stringify({ | |
| command: 'pengine_ask', | |
| pid: event.pid, | |
| query: 'ancestor_descendant(mike, Who)', | |
| options: '[limit(2)]' | |
| })); | |
| } else if (event.type == 'success') { | |
| document.getElementById("output").innerHTML += | |
| JSON.stringify(event.data) + "<br/>"; | |
| if (event.more) { | |
| ws.send(JSON.stringify({ | |
| command: 'pengine_next', | |
| pid: event.pid | |
| })); | |
| } | |
| } | |
| }; | |
| </script> | |
| </head> | |
| <body> | |
| <div id="output"></div> | |
| </body> | |
| </html> | |
| </pre> | |
| <p> | |
| To see the example in action, select the link below. A page will open in a new window or tab and show the output in the upper left corner. (Note that you can also use "View Page Source" to inspect the source code.) | |
| </p> | |
| <p> | |
| <a target="_blank" href="/apps/swish/ws-example-1.html"> | |
| <code>{{host}}/apps/swish/ws-example-1.html</code> | |
| </a> | |
| </p> | |
| <p> | |
| Our second example will inject the source code for a simple echo program in the spawned pengine and invite you to input Prolog terms to be echoed to a web page. Here's the HTML and JavaScript source code for the second application: | |
| </p> | |
| <pre> | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"/> | |
| <title>Example 2</title> | |
| <script> | |
| var ws = new WebSocket('ws://localhost:3060/ws','pcp-0.2'); | |
| var program = | |
| `echo :- | |
| pengine_input('Input a term!', Something), | |
| ( Something == null | |
| -> true | |
| ; pengine_output(Something), | |
| echo | |
| ).`; | |
| ws.onopen = function (message) { | |
| ws.send(JSON.stringify({ | |
| command: 'pengine_spawn', | |
| options: '[src_text("' + program + '")]' | |
| })); | |
| }; | |
| ws.onmessage = function (message) { | |
| var event = JSON.parse(message.data); | |
| if (event.type == 'spawned') { | |
| ws.send(JSON.stringify({ | |
| command: 'pengine_ask', | |
| pid: event.pid, | |
| query: 'echo' | |
| })); | |
| } else if (event.type == 'prompt') { | |
| var response = prompt(event.data); | |
| ws.send(JSON.stringify({ | |
| command: 'pengine_respond', | |
| pid: event.pid, | |
| term: response | |
| })); | |
| } else if (event.type == 'output') { | |
| document.getElementById("output").innerHTML += | |
| JSON.stringify(event.data) + "<br/>"; | |
| } | |
| }; | |
| </script> | |
| </head> | |
| <body> | |
| <div id="output"></div> | |
| </body> | |
| </html> | |
| </pre> | |
| <p> | |
| Again, to see what the example does, select the link below, then enter Prolog terms in the input widget that pops up and watch them being echoed to the page. | |
| </p> | |
| <p> | |
| <a target="_blank" href="/apps/swish/ws-example-2.html"> | |
| <code>{{host}}/apps/swish/ws-example-2.html</code> | |
| </a> | |
| </p> | |
| <div class="alert alert-info"> | |
| <p> | |
| <strong>Tip!</strong> Please don't let all this JavaScript scare you. For people not so keen on using JavaScript there are other languages that work in the browser, such as <a target="_blank" href="https://www.typescriptlang.org">TypeScript</a>, <a target="_blank" href="https://www.dartlang.org">Dart</a>, <a target="_blank" href="https://clojurescript.org">ClojureScript</a> and <a target="_blank" href="http://elm-lang.org/">Elm</a>. They can all be used instead of JavaScript. And if you don't need to run your app in a web browser, it's good to know that most major programming languages of today offer libraries supporting the WebSocket standard. | |
| </p> | |
| </div> | |
| <div class="alert alert-success"> | |
| <p> | |
| As it now stands, as a web programming language Web Prolog cannot compete with JavaScript in one important respect: web browsers support JavaScript, but not Prolog. This could change of course. Building an implementation that transpiles a subset of Web Prolog into JavaScript, or compiles it into <a target="_blank" href="https://en.wikipedia.org/wiki/WebAssembly">WebAssembly</a>, is most likely feasible. It needs to be able to spawn pengines or other actors on external nodes, and it must be able to communicate with them using send and receive in a completely asynchronous fashion. Note that we probably don't want Web Prolog running in a browser to act as a full-blown node, i.e. no external entity should be allowed to spawn a process on it. | |
| </p> | |
| </div> | |
| <h3>Using the HTTP /ask API</h3> | |
| <p> | |
| In addition to the asynchronous and stateful API described above, a Web Prolog node offers a <i>synchronous</i> and <i>stateless</i> web API using HTTP as a transport. Alternatively, although this isn't a term we like to use since it's so vague, this API can be described as <a target="_blank" href="https://en.wikipedia.org/wiki/Representational_state_transfer">RESTful</a>. In any case, statelessness is definitely part of being RESTful. | |
| </p> | |
| <p> | |
| To be considered stateless, a node should be able to (as it were) "forget" everything about the previous request as soon as it has returned a response to a query, and the next request to the node should be formed as if the previous one never occurred. It means, among other things, that uses of <a target="_blank" href="https://en.wikipedia.org/wiki/HTTP_cookie">HTTP cookies</a> and other ways to manage sessions are ruled out. | |
| </p> | |
| <p | |
| >In addition to a mandatory <code>query</code> parameter, the API also supports <code>template</code> and <code>limit</code> with semantics identical to the corresponding predicate options. More importantly, the API supports an <code>offset</code> parameter. Using a combination of <code>offset</code> and <code>limit</code>, a client can ask for a "slice" of solutions starting at the offset indicated, and with a length no greater than the value of the limit parameter. The <code>format</code> parameter determines the format of messages returned to the client. Currently <code>prolog</code>, <code>json</code> (default) and <code>json-s</code> are recognised values. | |
| </p> | |
| <p> | |
| Let's look at an example. When the link below is selected, a request to find a descendant of Mike is made: | |
| </p> | |
| <p> | |
| <a target="_blank" href="/ask?query=ancestor_descendant(mike,Who)"> | |
| <code>{{host}}/ask?query=ancestor_descendant(mike,Who)</code> | |
| </a> | |
| </p> | |
| <p> | |
| But that's just one of his descendants and we already know that they are three. We can retrieve the remaining answers by making the following request, with <code>offset</code> set to <code>1</code> this time, and with <code>limit</code> set to <code>2</code>: | |
| </p> | |
| <p> | |
| <a target="_blank" href="/ask?query=ancestor_descendant(mike,Who)&offset=1&limit=2"> | |
| <code>{{host}}/ask?query=ancestor_descendant(mike,Who)&offset=1&limit=2</code> | |
| </a> | |
| </p> | |
| <p> | |
| The challenge here has been to make this efficient when queries have more than one solution (and in particular if they are very many), i.e in cases when backtracking over HTTP is called for. Using our approach, it's likely (but not guaranteed) that the work that generated the first solution doesn't have to be repeated. Pengines are involved here too, but they are <i>anonymous</i>. An anonymous pengine running on the node serves as a form of <i>Prolog state cache</i>. The cached state of a pengine can only be used once though, and there's a possibility that another client made use of it before you had a chance to do so. If so, no real harm is done. | |
| </p> | |
| <p> | |
| If a client has asked for the first solution to a query <i>q</i>, and if <i>q</i> has more solutions, the chance is quite high that the client will ask for the next solution too. The node may therefore (subject to a setting) cache the state of the pengine that produced the first solution, so that the work spent on producing it will not have to be repeated. Note that this can be done without requiring that the node remembers <i>which</i> client made the request for the first solution. Indexing the relevant pengine on the combination of the query <i>q</i> and an integer <i>i</i> indicating the next state is sufficient. | |
| </p> | |
| <p> | |
| The underlying assumption here is that it's a good idea to sever the connection between a query and a process and allow the computation of the solutions to a query to be distributed over more than one pengine (i.e. to allow different pengines to work on the same query). We suspect that this will turn out to be a good way to save resources. Note that this means that the full responsibility for managing resources therefore lies with the owner of the node rather than with the client. | |
| </p> | |
| <!-- | |
| <p> | |
| In a real web browser application, <a target="_blank" href="https://en.wikipedia.org/wiki/XMLHttpRequest">XMLHttpRequest</a> would be used, or the more modern fetch. | |
| </p> | |
| --> | |
| <p> | |
| This API doesn't support I/O, and is therefore only suitable for applications in need of a "logic server". It furthermore doesn't support uses of assert and retract and it's not capable of supporting sessions. | |
| </p> | |
| <h3>Using the HTTP /send API</h3> | |
| <p> | |
| By means of the HTTP send API, and given a pid or a registered name, you are able to send a message to an actor with that pid or name. To demonstrate how this works, we ask you to once again subscribe to the node-resident publish-subscribe service: | |
| </p> | |
| <pre id="pubsub10"> | |
| ?- self(Self), | |
| pubsub_service ! subscribe(Self), | |
| repeat, | |
| io:write("Waiting for a message ..."), | |
| receive({ | |
| msg(Message) -> | |
| io:format("Received: ~p", [Message]), | |
| fail | |
| }). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#pubsub10")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Here's a link making an HTTP API request that will publish the term <code>hello</code> to the service which will then forward it to all subscribers: | |
| </p> | |
| <p> | |
| <a target="_blank" href="/send?name=pubsub_service&message=publish(hello)"> | |
| <code>{{host}}/send?name=pubsub_service&message=publish(hello)</code> | |
| </a> | |
| </p> | |
| <p> | |
| You should now have received the message "hello". Such a request can be made by any client that knows the name of the service. | |
| </p> | |
| <div class="alert alert-danger"> | |
| <strong>BUG!</strong> Not really a bug... just a reminder to ourselves that this needs to work with a POST request as well. | |
| </div> | |
| <h2>High-level remote procedure calls</h2> | |
| <p> | |
| We have already shown how to create remote pengines or other actors using the <code>node</code> option of <code>pengine_spawn/1-2</code> and <code>spawn/2-3</code>. In both cases communication with the remote actor is asynchronous and uses the WebSocket transport. Web Prolog also supports two kinds of more high-level predicate APIs for making remote procedure calls, one which is synchronous and very easy to use, and one which is asynchronous and somewhat harder to use. In both cases and in contrast to <code>pengine_spawn/1-2</code> and <code>spawn/2-3</code> they don't support sessions, only one-shot queries are allowed. | |
| </p> | |
| <h3>Non-deterministic RPC</h3> | |
| <p> | |
| Web Prolog offers a predicate <code>rpc/2-3</code> capable of making non-deterministic remote procedure calls. Here's our first example of its use: | |
| </p> | |
| <pre id="rpc-http"> | |
| ?- rpc('{{host}}', member(X,[a,b,c])). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#rpc-http")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| By default, <code>rpc/2-3</code> uses the RESTful HTTP API to communicate with the remote node. | |
| <p> | |
| The following example shows that thanks to the Prolog state cache the first (slow) solution isn't recomputed when we ask for the second one, despite the fact that <code>rpc/2</code> makes two totally separate HTTP requests: | |
| </p> | |
| <pre id="rpc-http2"> | |
| ?- rpc('{{host}}', (sleep(1),X=a ; X=b)). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#rpc-http2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| For the purpose of reducing the number of network roundtrips, the option | |
| <code>limit</code> (inherited from <code>pengine_ask/3</code>) may be passed: | |
| </p> | |
| <pre id="rpc-limit"> | |
| ?- rpc('{{host}}', between(1, 12, N), [ | |
| limit(5) | |
| ]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#rpc-limit")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| What this example tries to convey is that although the call only needs to make three network roundtrips | |
| in order to generate all twelve solutions, the behaviour of the call, as seen from the point | |
| of view of the client, doesn't change. Indeed, the <code>limit</code> option serves as a | |
| performance optimisation only, and has nothing to do with logic and the declarative reading | |
| of the query. | |
| </p> | |
| <p> | |
| The <code>transport</code> option allows us to use the WebSocket transport instead of HTTP. In order to inject code into the process, the option <code>src_text</code> (inherited from <code>pengine_spawn/2</code> which in turn inherited it from <code>spawn/3</code>) may be passed: | |
| </p> | |
| <pre id="rpc-ws"> | |
| ?- rpc('{{host}}', p(X), [ | |
| transport(ws), | |
| src_text("p(a). p(b). p(c).") | |
| ]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#rpc-ws")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <div class="alert alert-danger"> | |
| <p> | |
| <strong>BUG!</strong> The RESTful HTTP API should also be able to support code injection, but doesn't at the moment. Therefore, an attempt to use code injection when passing the option <code>transport(http)</code> to <code>rpc/2-3</code> has no effect either. | |
| </p> | |
| </div> | |
| <h3>Bringing data to the code</h3> | |
| <p> | |
| The special purpose URI <code>localnode</code> serves as the default value of the <code>node</code> option for both <code>spawn/3</code> and <code>pengine_spawn/2</code>. Thus it follows, perhaps a bit counter-intuitively, that <code>rpc/3</code> in combination with the <code>src_uri</code> option can also be used to "bring data to the code": | |
| </p> | |
| <pre id="rpc-localnode"> | |
| ?- rpc(localnode, p(X), [ | |
| src_uri('https://gist.githubusercontent.com/torbjornlager/21583e7238dc855f94f6b5f2f48788bb/raw/p.pl') | |
| ]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#rpc-localnode")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <h3>A note on the implementation of non-deterministic RPC</h3> | |
| <p> | |
| The <code>rpc/2-3</code> predicate has two implementations, one using the HTTP transport and the other using the WebSocket transport. Here's the implementation being executed when <code>transport(http)</code> is passed (which is the default): | |
| </p> | |
| <pre> | |
| rpc_http(URI, Query, Options) :- | |
| option(limit(Limit), Options, 1), | |
| rpc_http(URI, Query, 0, Limit). | |
| rpc_http(URI, Query, Offset, Limit) :- | |
| parse_url(URI, Parts), | |
| format(atom(QueryAtom), "(~p)", [Query]), | |
| rpc_http(Query, Offset, Limit, QueryAtom, Parts). | |
| rpc_http(Query, Offset, Limit, QueryAtom, Parts) :- | |
| parse_url(ExpandedURI, [ path('/ask'), | |
| search([ query=QueryAtom, | |
| offset=Offset, | |
| limit=Limit, | |
| format=prolog | |
| ]) | |
| | Parts]), | |
| setup_call_cleanup( | |
| http_open(ExpandedURI, Stream, []), | |
| read(Stream, Answer), | |
| close(Stream)), | |
| wait_answer(Answer, Query, Offset, Limit, QueryAtom, Parts). | |
| wait_answer(error(anonymous, Error), _, _, _, _, _):- | |
| throw(Error). | |
| wait_answer(failure(anonymous), _, _, _, _, _):- | |
| fail. | |
| wait_answer(success(anonymous, Solutions, false), Query, _, _, _, _) :- | |
| !, | |
| member(Query, Solutions). | |
| wait_answer(success(anonymous, Solutions, true), | |
| Query, Offset0, Limit, QueryAtom, Parts) :- | |
| ( member(Query, Solutions) | |
| ; Offset is Offset0 + Limit, | |
| rpc_http(Query, Offset, Limit, QueryAtom, Parts) | |
| ). | |
| </pre> | |
| <p> | |
| This (admittedly somewhat hairy) implementation uses the SWI-Prolog specific libraries – <code>library(url)</code> and the HTTP client implemented by <code>library(http/http_open)</code> – to interact with the HTTP API of a node. | |
| </p> | |
| <p> | |
| When the <code>transport(ws)</code> is passed, an alternative and arguably more elegant implementation based on <code>pengine_spawn/2</code> and <code>pengine_ask/3</code> (and therefore indirectly on the WebSocket protocol) is selected. The predicate <code>rpc_ws/3</code> is implemented like so: | |
| </p> | |
| <pre> | |
| rpc_ws(URI, Query, Options) :- | |
| pengine_spawn(Pid, [ | |
| node(URI), | |
| exit(true), | |
| monitor(false) | |
| | Options | |
| ]), | |
| pengine_ask(Pid, Query, Options), | |
| wait_answer(Query, Pid). | |
| wait_answer(Query, Pid) :- | |
| receive({ | |
| failure(Pid) -> fail; | |
| error(Pid, Exception) -> | |
| throw(Exception); | |
| success(Pid, Solutions, true) -> | |
| ( member(Query, Solutions) | |
| ; pengine_next(Pid), | |
| wait_answer(Query, Pid) | |
| ); | |
| success(Pid, Solutions, false) -> | |
| member(Query, Solutions) | |
| }). | |
| </pre> | |
| <p> | |
| The first clause of the implementation shows clearly how the inheritance of options from <code>pengine_spawn/2</code> and <code>pengine_ask/3</code> to <code>rpc/2-3</code> works. Note that the value of an option passed explicitly to <code>pengine_spawn/2</code> takes precedence over the value of the same option if it's given in the third argument of <code>rpc/3</code>. Thus, the only options that can have an effect in a call to <code>rpc/3</code> are <code>limit</code>, <code>timeout</code> and the <code>src_*</code> options. | |
| </p> | |
| <p> | |
| Probably the most interesting parts of this implementation are the use of the disjunction in the body of the third receive clause and the use of <code>member/2</code> in the third and fourth clauses. They are responsible for turning the deterministic calls made by <code>pengine_ask/3</code> and <code>pengine_next/1</code> into the non-deterministic behaviour that we want <code>rpc/2-3</code> to show. | |
| </p> | |
| <h3>Promises (asynchronous RPC)</h3> | |
| <p> | |
| A <em>promise</em> is a type of RPC method which is asynchronous and thus doesn't suspend the caller until the result is computed. Instead, a reference is immediately returned, which can later be used by <code>yield/2-3</code> (which must be running in the same process) to collect the answer. The reference can be viewed as a promise to deliver the answer. Let's try this: | |
| </p> | |
| <pre id="promise"> | |
| ?- promise('{{host}}', member(X,[a,b,c,d]), Reference). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#promise")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| The predicate <code>yield/2-3</code> returns the promised answer from a previous call to <code>promise/3-4</code>. If the answer is available, it's returned immediately. Otherwise, the calling process is suspended until the answer arrives from the node that was called.</p> | |
| <pre id="yield-timeout"> | |
| ?- yield($Reference, Answer, [ | |
| timeout(3), | |
| on_timeout(Answer=timeout) | |
| ]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#yield-timeout")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| To get a glimpse of the behaviour when no answer is available, you may want to run the query again ... | |
| </p> | |
| <p>The above use of <code>promise/3-4</code> and <code>yield/3</code> only gave the first answer to the query. To produce other answers, we need to pass the option <code>offset</code>. The <code>limit</code> option is also available, allowing us to ask for more than one answer at a time:</p> | |
| <pre id="promise-offset1"> | |
| ?- promise('{{host}}', member(X,[a,b,c,d]), Reference, [ | |
| template(X), | |
| offset(1), | |
| limit(2) | |
| ]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#promise-offset1")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p>Let's call <code>yield/3</code> again:</p> | |
| <pre id="promise-offset2"> | |
| ?- yield($Reference, Answer, [ | |
| timeout(0.5), | |
| on_timeout(fail) | |
| ]). | |
| </pre> | |
| <p class="ask-buttons"> | |
| <button onclick='pasteAndAskExample("#promise-offset2")' class="btn btn-xs btn-primary" type="button">Ask | |
| </button> | |
| </p> | |
| <p> | |
| Note that if we would now request the remaining answers by re-running the call to <code>promise/4</code> with <code>offset</code> set to <code>3</code> this time, it's likely (but not guaranteed) that the work that generated the first three solutions does not have to be repeated. Again, this is due to the RESTfulness of the HTTP API, where an anonymous pengine running on the node serves as a form of cache. | |
| </p> | |
| <p> | |
| Note that <code>promise/4</code> only operates over HTTP, which is useful if a WebSocket connection is not available. | |
| </p> | |
| <h2>An implementation in Erlang?</h2> | |
| <p> | |
| An implementation of a Web Prolog node in Erlang would be interesting since it would most probably have a performance profile different from the implementation in SWI-Prolog. The basic Prolog machinery (e.g. unification, backtracking and clause indexing) is likely to be slower in Erlang compared to (say) SWI-Prolog, whereas the super fast lightweight processes of Erlang have other advantages, probably allowing it to scale better to very many simultaneous users on a network. For the networking part, we note that Erlang is particularly famous for extremely efficient implementations of web-related technologies such as web servers (e.g. Yaws and Cowboy). However, when Web Prolog is used inside a LAN, Erlang's normal way of communication between nodes would be hard to beat. | |
| </p> | |
| <p> | |
| Interestingly, there is <a target="_blank" href="https://github.com/rvirding/erlog"><em>Erlog</em></a> – an implementation of Prolog in Erlang, written by Robert Virding, which might serve as a nice point of departure for a Web Prolog implementation. | |
| </p> | |
| <h2>Documentation</h2> | |
| <p> | |
| Many of the predicates available in SWI-Prolog are available in this tutorial as well. Refer to <a target="_blank" href="http://www.swi-prolog.org/pldoc/doc_for?object=manual">the SWI-Prolog manual</a> for documentation. The predicates peculiar to Web Prolog are documented below. However, note that everything about Web Prolog is still experimental and in flux. | |
| </p> | |
| <h3>The actor API</h3> | |
| <p><b>Predicate:</b> <code>self/1</code></p> | |
| <pre> | |
| self(-Pid) is det. | |
| </pre> | |
| <p> | |
| Binds <code>Pid</code> to the process identifier of the calling process. | |
| </p> | |
| <p><b>Predicate:</b> <code>spawn/2-3</code></p> | |
| <pre> | |
| spawn(+Goal, -Pid) is det. | |
| spawn(+Goal, -Pid, +Options) is det. | |
| </pre> | |
| <p> | |
| Creates a new Web Prolog process running <code>Goal</code>. Valid options are: | |
| </p> | |
| <ul> | |
| <li> | |
| <code>node(+URI)</code> | |
| URI points to the Prolog Web node on which to create the process. | |
| Default is the current node <code>localnode</code>. | |
| <li> | |
| <code>monitor(+Boolean)</code> | |
| Default is to not monitor. | |
| <li> | |
| <code>link(+Boolean)</code> | |
| Default is to link. | |
| <li> | |
| <code>timeout(+IntegerOrFloat)</code> | |
| Terminates the spawned process (or the process of spawning a process) after <code>IntegerOrFloat</code> seconds. | |
| <li> | |
| <code>src_text(+AtomOrString)</code> | |
| Injects the clauses specified by a source text into the | |
| process. | |
| <li> | |
| <code>src_list(+ListOfClauses)</code> | |
| Injects a list of Web Prolog clauses into the process. | |
| <li> | |
| <code>src_uri(+URI)</code> | |
| Injects the clauses specified in the source code located at <code>URI</code> into | |
| the process. | |
| <li> | |
| <code>src_predicates(+List)</code> | |
| Injects the local predicates denoted by <code>List</code> into the | |
| process. <code>List</code> is a list of predicate indicators. | |
| </ul> | |
| <p><b>Predicate:</b> <code>!/2</code></p> | |
| <pre> | |
| +PidOrName ! +Message is det. | |
| send(+PidOrName, +Message) is det. | |
| </pre> | |
| <p> | |
| Sends <code>Message</code> to the mailbox of the process identified as <code>PidOrName</code>. A message can be any Web Prolog term. The sending is asynchronous, i.e. <code>!/2</code> does not block waiting for a response but continues immediately. Also, <code>!/2</code> does not throw exceptions, so if a process named <code>Pid</code> does not exist, nothing happens. | |
| </p> | |
| <p><b>Predicate:</b> <code>receive/1-2</code></p> | |
| <pre> | |
| receive(+Clauses) is semidet. | |
| receive(+Clauses, +Options) is semidet. | |
| </pre> | |
| <p> | |
| <code>Clauses</code> is a sequence of receive clauses delimited by a semicolon: | |
| </p> | |
| <pre> | |
| { Pattern1 [when Guard1] -> | |
| Body1 ; | |
| ... | |
| PatternN [when GuardN] -> | |
| BodyN | |
| } | |
| </pre> | |
| <p> | |
| Each pattern in turn is matched against the first message (the one that has been waiting longest) in the mailbox. If a pattern matches and the corresponding guard succeeds, the matching message is removed from the mailbox and the corresponding body is evaluated. If the first message is not accepted, the second one will be tried, then the third, and so on. If none of the messages in the mailbox is accepted, the process will wait for new messages, checking them one at a time in the order they arrive. Messages in the mailbox that are not accepted are left in the mailbox without any change in their contents or order. | |
| </p> | |
| <p> | |
| Valid options are: | |
| </p> | |
| <ul> | |
| <li> | |
| <code>timeout(+IntegerOrFloat)</code> | |
| If nothing appears in the current mailbox within <code>IntegerOrFloat</code> seconds, the predicate succeeds anyway. Default is no timeout. | |
| <li> | |
| <code>on_timeout(+Goal)</code> | |
| If the timeout occurs, <code>Goal</code> is called. | |
| </ul> | |
| <p><b>Predicate:</b> <code>exit/1-2</code></p> | |
| <pre> | |
| exit(+Reason) is det. | |
| exit(+PidOrName, +Reason) is det. | |
| </pre> | |
| <p> | |
| Executing <code>exit/1</code> terminates the current process. The predicate <code>exit/2</code> can be used to terminate any process, but only if you know its pid or its registered name, and only if you own it. | |
| </p> | |
| <h3>The pengine API</h3> | |
| <p><b>Predicate:</b> <code>pengine_spawn/1-2</code></p> | |
| <pre> | |
| pengine_spawn(-Pid) is det | |
| pengine_spawn(-Pid, +Options) is det | |
| </pre> | |
| <p> | |
| The predicate <code>pengine_spawn/1-2</code> inherits all options from | |
| <code>spawn/3</code> and adds a new one as well. Options inherited from <code>spawn/3</code> means that code may be injected into pengine processes and that processes can be monitored. The option that is added is: | |
| </p> | |
| <ul> | |
| <li> | |
| <code>exit(+Boolean)</code> | |
| Determines if the pengine session must exit after having run a | |
| goal to completion. Defaults to <code>true</code>. | |
| </ul> | |
| <p><b>Predicate:</b> <code>pengine_ask/2-3</code></p> | |
| <pre> | |
| pengine_ask(+Pid, +Goal) is det. | |
| pengine_ask(+Pid, +Goal, +Options) is det | |
| </pre> | |
| <p> | |
| Calls pengine <code>Pid</code> with the goal <code>Goal</code>. Valid options are: | |
| </p> | |
| <ul> | |
| <li> | |
| <code>template(+Template)</code> | |
| <code>Template</code> is a variable (or a term | |
| containing variables) shared with the query. By default, the template | |
| is identical to the goal. | |
| <li> | |
| <code>limit(+Integer)</code> | |
| Retrieve solutions in lists of length <code>Integer</code> rather than one by one. A | |
| value of 1 means a unary list (default). Other integers indicate | |
| the maximum number of solutions to retrieve in one batch. | |
| </ul> | |
| <p> | |
| Note that <code>pengine_ask/2-3</code> is deterministic, even for queries that have more than one solution. Also, keep in mind that variables in <code>Goal</code> | |
| will not be bound. Instead, results and other kinds of output will be returned in the form of messages delivered to the mailbox of the process that called <code>pengine_spawn/2-3</code>. | |
| </p> | |
| <ul> | |
| <li> | |
| <code>success(Pid, Terms, More)</code> | |
| <code>Pid</code> refers to the pengine | |
| that succeeded in solving the query. <code>Terms</code> is a list holding | |
| instantiations of <code>Template</code>. <code>More</code> is either <code>true</code> or | |
| <code>false</code>, indicating whether we can expect the pengine to be | |
| able to return more solutions or not, would we call <code>pengine_next/1-2</code>. | |
| <li> | |
| <code>failure(Pid)</code> | |
| <code>Pid</code> is the pid of the pengine that failed | |
| for lack of (more) solutions. | |
| <li> | |
| <code>error(Pid, Term)</code> | |
| <code>Pid</code> is the pid of the pengine throwing the | |
| exception. <code>Term</code> is the exception's error term. | |
| <li> | |
| <code>output(Pid, Term)</code> | |
| <code>Pid</code> is the pid of a pengine running the goal that called | |
| <code>pengine_output/1</code>. <code>Term</code> is the term that was passed in the | |
| argument of <code>pengine_output/1</code> when it was called. | |
| <li> | |
| <code>prompt(Pid, Term)</code> | |
| <code>Pid</code> is the pid of the pengine that called <code>pengine_input/2</code> | |
| and <code>Term</code> is the prompt. | |
| <li> | |
| <code>down(Pid, Term)</code> | |
| <code>Pid</code> is the pid of the pengine that terminated and <code>Term</code> is the reason. | |
| </ul> | |
| <p><b>Predicate:</b> <code>pengine_next/1-2</code></p> | |
| <pre> | |
| pengine_next(+Pid) is det. | |
| pengine_next(+Pid, +Options) is det | |
| </pre> | |
| <p> | |
| Asks pengine <code>Pid</code> for the next solution to <code>Goal</code>. The only valid option is: | |
| </p> | |
| <ul> | |
| <li> | |
| <code>limit(+Integer)</code> | |
| Retrieve solutions in a list of length <code>Integer</code> rather | |
| than one by one. 1 means no limiting (default). Other positive integers | |
| indicate the maximum number of solutions to retrieve at once. | |
| </ul> | |
| <p> | |
| The messages delivered to the mailbox of the process that called <code>pengine_next/1-2</code> are the same as for <code>pengine_ask/2-3</code>. | |
| </p> | |
| <p><b>Predicate:</b> <code>pengine_stop/1</code></p> | |
| <pre> | |
| pengine_stop(+Pid) is det. | |
| </pre> | |
| <p> | |
| Asks pengine <code>Pid</code> to stop. If successful, delivers a message <code>stop(Pid)</code> to the mailbox of the process that called <code>pengine_spawn/2-3</code>. | |
| </p> | |
| <p><b>Predicate:</b> <code>pengine_abort/1</code></p> | |
| <pre> | |
| pengine_abort(+Pid) is det. | |
| </pre> | |
| <p> | |
| Tells pengine <code>Pid</code> to abort any goal that it currently runs. If successful, delivers a message <code>abort(Pid)</code> to the mailbox of the process that called <code>pengine_spawn/2-3</code>. | |
| </p> | |
| <p><b>Predicate:</b> <code>pengine_output/1</code></p> | |
| <pre> | |
| pengine_output(+Term) is det. | |
| </pre> | |
| <p> | |
| Sends <code>Term</code> to the parent process. | |
| </p> | |
| <p><b>Predicate:</b> <code>pengine_input/2</code></p> | |
| <pre> | |
| pengine_input(+Prompt, -Term) is det. | |
| </pre> | |
| <p> | |
| Sends <code>Prompt</code> to the parent process and waits for input. <code>Prompt</code> may be any term, compound as well as atomic. | |
| </p> | |
| <p><b>Predicate:</b> <code>pengine_respond/2</code></p> | |
| <pre> | |
| pengine_respond(+Pid, +Input) is det. | |
| </pre> | |
| <p> | |
| Sends a response in the form of the term <code>Input</code> to a process | |
| that has prompted its parent process for input. | |
| </p> | |
| <p><b>Predicate:</b> <code>pengine_exit/1-2</code></p> | |
| <pre> | |
| pengine_exit(+Pid) is det. | |
| pengine_exit(+Pid, +Reason) is det. | |
| </pre> | |
| <p> | |
| Same as <code>exit/1</code> and <code>exit/2</code>. | |
| </p> | |
| <h3>High-level RPC predicates</h3> | |
| <p> | |
| <b>Predicate:</b> <code>rpc/2-3</code> | |
| </p> | |
| <pre> | |
| rpc(+URI, +Query) is nondet. | |
| rpc(+URI, +Query, +Options) is nondet. | |
| </pre> | |
| <p> | |
| Semantically equivalent to the sequence below, except that the query is executed in (and in the Prolog context of) the node referred to by <code>URI</code>, rather than locally. | |
| </p> | |
| <pre> | |
| copy_term(Query, Copy), | |
| call(Copy), % executed on node at URI | |
| Query = Copy. | |
| </pre> | |
| <p> | |
| All the options for <code>pengine_spawn/3</code> are valid for <code>rpc/3</code> too, except for <code>node</code>, <code>exit</code>, <code>link</code>, and <code>monitor</code>. | |
| </p> | |
| <p> | |
| <b>Predicate:</b> <code>promise/3-4</code> | |
| </p> | |
| <pre> | |
| promise(+URI, +Query, -Reference) is det. | |
| promise(+URI, +Query, -Reference, +Options) is det. | |
| </pre> | |
| <p> | |
| Make asynchronous RPC call to node <code>URI</code> with <code>Query</code>. This is a type of RPC that does not suspend the caller until the result is | |
| computed. Instead, a reference is returned, which can later be | |
| used by <code>yield/2-3</code> to collect the answer. The reference can be | |
| viewed as a promise to deliver the answer. Valid options are <code>template</code>, <code>offset</code>, <code>limit</code> and <code>timeout</code>. [Timeout is not yet implemented] | |
| </p> | |
| <p> | |
| <b>Predicate:</b> <code>yield/2-3</code> | |
| </p> | |
| <pre> | |
| yield(+Reference, ?Message) is det. | |
| yield(+Reference, ?Message, +Options) is det. | |
| </pre> | |
| <p> | |
| Returns the promised answer from a previous call to <code>promise/3-4</code>. | |
| If the answer is available, it is returned immediately. Otherwise, | |
| the calling process is suspended until the answer arrives from the | |
| node that was called. Valid options are <code>timeout(Integer)</code> (default is <code>inf</code>) and <code>on_timeout(Goal)</code> (default is <code>fail</code>). | |
| </p> | |
| <p> | |
| Note that this predicate must be called by the same process from | |
| which the previous call to <code>promise/3-4</code> was made, otherwise it will | |
| not return. | |
| </p> | |
| <!-- | |
| <h2>What does Web Prolog include?</h2> | |
| <p> | |
| Except for the spawning and messaging primitives, we haven't said much about what should be included in Web Prolog. It's of course only natural and very convenient to take the syntax and semantics and the built-in predicates of our implementation language SWI-Prolog as our point of departure. At this point in time, most built-in predicates and most libraries supported by SWI-Prolog are indeed supported also by Web Prolog – they are more or less one and the same language. This may not always remain the case. An ambition to support interoperability with other future implementations of Web Prolog necessitates a tighter grip on the language. In the mean time, please consult the SWI-Prolog <a target="_blank" href="http://www.swi-prolog.org/pldoc/doc_for?object=manual">manual</a>. | |
| </p> | |
| <h2>What should we use for distribution?</h2> | |
| <p> | |
| Web Prolog supports no less than four ways to do distributed programming: | |
| </p> | |
| <ol> | |
| <li> | |
| Use <code>spawn/3</code> with the <code>node</code> option set to a URI value pointing to a remote node. This is the simplest approach to distribution and closely mirrors how one would use the related Erlang primitives. The communication with the remote actor is asynchronous. It also allows you to run several actors concurrently. The WebSocket transport is used in the communication with the remote node. | |
| </li> | |
| <li> | |
| Use <code>pengine_spawn/2</code> with the <code>node</code> option set to a URI value pointing to a remote node. This gives us a first-class Prolog top-level running on the remote node and is the obvious choice when dealing with queries with more than one solution. The communication with the remote pengine is asynchronous. It also allows you to run several pengines concurrently. The <code>limit</code> option allows you to implement pagination of solutions. This predicate uses the WebSocket transport in its communication with the remote node. | |
| </li> | |
| <li> | |
| Use <code>rpc/2-3</code>. This implements non-deterministic remote procedure calls. When the client is Prolog-based (i.e. when it's also a Web Prolog node), this is by far the easiest way to make a remote call. It doesn't involve any concurrency, however, and it's a synchronous construct. You may choose transport – HTTP (default) or WebSocket – and the <code>limit</code> option allows you to limit the number of network roundtrips needed to run a query to completion. | |
| </li> | |
| <li> | |
| Use <code>promise/3-4</code> in combination with <code>yield/2-3</code>. Since <code>promise/3-4</code> doesn't wait for the answer to arrive, it's an asynchronous alternative to <code>rpc/2-3</code>. The waiting is instead done by <code>yield/2-3</code>. This predicate uses the HTTP transport in its communication with the remote node. Should WebSocket be available, you may want to use <code>pengine_spawn/2</code> as another asynchronous alternative to <code>rpc/2-3</code>. | |
| </li> | |
| </ol> | |
| <p> | |
| Some of the complexity is due to the fact that we are in the process of defining a profile of Web Prolog that only accepts the use of HTTP as the underlying transport. A Prolog client (e.g. a pengine) will then only be able to use <code>rpc/2-3</code> (with transport set to the default HTTP) or <code>promise/3-4</code> and <code>yield/2-3</code> in its communication with such a node. The point is that such a node would be considerably easier to implement, while still being useful as the back-end to many applications. | |
| </p> | |
| --> | |
| <!-- | |
| <h2>Questionnaire</h2> | |
| --> | |
| </div> | |
| <script> | |
| function recursiveReplaceHost(node, host) { | |
| if (node.nodeType == 3) { // text node | |
| node.nodeValue = node.nodeValue.replace(/{{host}}/g, host); | |
| } else if (node.nodeType == 1) { // element | |
| $(node).contents().each(function () { | |
| recursiveReplaceHost(this, host); | |
| }); | |
| } | |
| } | |
| function recursiveReplacePengine(node, host) { | |
| if (node.nodeType == 3) { // text node | |
| node.nodeValue = node.nodeValue.replace(/{{pengine}}/g, host); | |
| } else if (node.nodeType == 1) { // element | |
| $(node).contents().each(function () { | |
| recursiveReplacePengine(this, host); | |
| }); | |
| } | |
| } | |
| function consult(id) { | |
| if (gterm.get_prompt().trim() != "?-") { | |
| gterm.exec(""); | |
| } | |
| setTimeout(function() { | |
| var val = $(id).text(); | |
| var program = val.replace(/'/g,"\\'"); | |
| gterm.echo("\n?- consult_text('"+id+"')."); | |
| gterm.exec("consult_text('"+program+"').", true); | |
| setTimeout(gterm.enable, 0); | |
| }, 5) | |
| } | |
| function paste(query) { | |
| if (gterm.get_prompt().trim() != "?-") { | |
| gterm.exec(""); | |
| } | |
| setTimeout(function(){ | |
| gterm.insert(query) | |
| setTimeout(gterm.enable, 0); | |
| }, 5); | |
| } | |
| function pasteExample(id) { | |
| if (gterm.get_prompt().trim() != "?-") { | |
| gterm.exec(""); | |
| } | |
| setTimeout(function(){ | |
| gterm.insert($(id).text().slice(3).trim()) | |
| setTimeout(gterm.enable, 0); | |
| }, 5); | |
| } | |
| function pasteExample(id) { | |
| if (gterm.get_prompt().trim() != "?-") { | |
| gterm.exec(""); | |
| } | |
| setTimeout(function(){ | |
| gterm.insert($(id).text().slice(3).trim()) | |
| setTimeout(gterm.enable, 0); | |
| }, 5); | |
| } | |
| function pasteAndAskExample(id) { | |
| if (gterm.get_prompt().trim() != "?-") { | |
| gterm.exec(""); | |
| } | |
| setTimeout(function(){ | |
| gterm.exec($(id).text().slice(3).trim()) | |
| setTimeout(gterm.enable, 0); | |
| }, 5); | |
| } | |
| </script> | |
| </body> | |
| </html> |